diff --git a/.github/workflows/beta.yml b/.github/workflows/beta.yml index 3f233c8dcfd..41577082aee 100644 --- a/.github/workflows/beta.yml +++ b/.github/workflows/beta.yml @@ -4,6 +4,7 @@ on: push: branches: - beta + - 'beta-*' workflow_dispatch: diff --git a/Nicegram/NGAppCache/Sources/AppCache.swift b/Nicegram/NGAppCache/Sources/AppCache.swift index 7c98ae2b2fc..6ea85315c6d 100644 --- a/Nicegram/NGAppCache/Sources/AppCache.swift +++ b/Nicegram/NGAppCache/Sources/AppCache.swift @@ -9,9 +9,6 @@ public final class AppCache { @UserDefaultsBacked(key: "firstAppLaunchDate", storage: .standard, defaultValue: nil) public static var firstAppLaunchDate: Date? - @UserDefaultsBacked(key: "currentProductID", storage: .standard, defaultValue: nil) - public static var currentProductID: String? - @UserDefaultsBacked(key: "currentUserID", storage: .standard, defaultValue: nil) public static var currentUserID: String? @@ -20,9 +17,6 @@ public final class AppCache { @UserDefaultsBacked(key: "wasLauchedBefore", storage: .standard, defaultValue: false) private static var _wasLauchedBefore: Bool - - @UserDefaultsBacked(key: "hasUnlimPremium", storage: .standard, defaultValue: false) - public static var hasUnlimPremium: Bool @UserDefaultsBacked(key: "wasOnboardingShown", storage: .standard, defaultValue: false) public static var wasOnboardingShown: Bool @@ -38,10 +32,6 @@ public final class AppCache { _wasLauchedBefore = newValue } } - - public static var haveValidSubscription: Bool { - return currentProductID != nil || hasUnlimPremium - } public static var mobileIdentifier: String { if let identifier = KeychainWrapper.standard.string(forKey: "ng_mobileIdentifier", withAccessibility: .afterFirstUnlock), diff --git a/Nicegram/NGCopyProtectedContent/BUILD b/Nicegram/NGCopyProtectedContent/BUILD index c8a2d1a287d..0b982662aa7 100644 --- a/Nicegram/NGCopyProtectedContent/BUILD +++ b/Nicegram/NGCopyProtectedContent/BUILD @@ -9,7 +9,7 @@ swift_library( deps = [ "//Nicegram/NGData:NGData", "//Nicegram/NGRemoteConfig:NGRemoteConfig", - "@swiftpkg_nicegram_assistant_ios//:Sources_NGPremiumUI", + "@swiftpkg_nicegram_assistant_ios//:Sources_FeatPremiumUI", ], visibility = [ "//visibility:public", diff --git a/Nicegram/NGCopyProtectedContent/Sources/CopyProtectedContent.swift b/Nicegram/NGCopyProtectedContent/Sources/CopyProtectedContent.swift index 4f68b1ec162..7b1ef495f2e 100644 --- a/Nicegram/NGCopyProtectedContent/Sources/CopyProtectedContent.swift +++ b/Nicegram/NGCopyProtectedContent/Sources/CopyProtectedContent.swift @@ -1,6 +1,6 @@ +import FeatPremiumUI import Foundation import NGData -import NGPremiumUI import NGRemoteConfig import Postbox import UIKit diff --git a/Nicegram/NGData/Sources/NGSettings.swift b/Nicegram/NGData/Sources/NGSettings.swift index 586cb288c04..76915b7ca61 100644 --- a/Nicegram/NGData/Sources/NGSettings.swift +++ b/Nicegram/NGData/Sources/NGSettings.swift @@ -1,3 +1,4 @@ +import FeatPremium import Foundation import NGAppCache @@ -130,7 +131,13 @@ public var VarNGSharedSettings = NGSharedSettings() public func isPremium() -> Bool { - return AppCache.haveValidSubscription + if #available(iOS 13.0, *) { + return PremiumContainer.shared + .getPremiumStatusUseCase() + .hasPremiumOnDeviceNonIsolated() + } else { + return false + } } public func usetrButton() -> [(Bool, [String])] { diff --git a/Nicegram/NGData/Sources/NGWeb.swift b/Nicegram/NGData/Sources/NGWeb.swift index 71d133311f0..3a275c97e22 100644 --- a/Nicegram/NGData/Sources/NGWeb.swift +++ b/Nicegram/NGData/Sources/NGWeb.swift @@ -171,68 +171,3 @@ public func updateNGInfo(userId: Int64) { ngLog("SYNC_CHATS \(VARNGAPISETTINGS.SYNC_CHATS)\nRESTRICTED \(VARNGAPISETTINGS.RESTRICTED)\nALLOWED \(VARNGAPISETTINGS.ALLOWED)\nRESTRICTED_REASONS count \(VARNGAPISETTINGS.RESTRICTION_REASONS.count)\nPREMIUM", LOGTAG) }) } - - -public func requestValidator(_ receipt: Data, completion: @escaping (_ apiResult: String) -> Void) { - let startTime = CFAbsoluteTimeGetCurrent() - ngLog("DECLARING REQUEST VALIDATOR", LOGTAG) - let urlString = NGENV.validator_url - - let url = URL(string: urlString)! - var request : URLRequest = URLRequest(url: url) - request.httpBody = receipt - request.httpMethod = "POST" - let task = URLSession.shared.dataTask(with: request) { (data, response, error) in - let timeElapsed = CFAbsoluteTimeGetCurrent() - startTime - ngLog("PROCESSED REQUEST VALIDATOR IN \(timeElapsed) s.", LOGTAG) - if let error = error { - ngLog("Error validating: \(error)", LOGTAG) - completion("error") - } else { - if let response = response as? HTTPURLResponse { - if response.statusCode == 200 { - if let data = data, let dataString = String(data: data, encoding: .utf8) { - completion(dataString) - } - } - } - } - } - task.resume() -} - - - -public func validatePremium(_ current: Bool, forceValid: Bool = false) { - let sem = DispatchSemaphore(value: 0) - if (current) { - guard let receiptURL = Bundle.main.appStoreReceiptURL, - let data = try? Data(contentsOf: receiptURL) else { - // NGSettings.premium = false - ngLog("Hello hacker?????", LOGTAG) - sem.signal() - return - } - - let encodedData = data.base64EncodedData(options: []) - requestValidator(encodedData, completion: { validStatus in - if validStatus == "0" { // Hacker - // NGSettings.premium = false - ngLog("Hello hacker", LOGTAG) - // preconditionFailure("Hello hacker") - } else if validStatus == "1" { // OK - ngLog("Okie-dokie", LOGTAG) - } else { // Error - if forceValid { - // NGSettings.premium = false - ngLog("Hello hacker?", LOGTAG) - } - } - sem.signal() - return - }) - } - sem.wait() - return -} - diff --git a/Nicegram/NGEnv/Sources/NGEnv.swift b/Nicegram/NGEnv/Sources/NGEnv.swift index 309702648b2..d610c346832 100644 --- a/Nicegram/NGEnv/Sources/NGEnv.swift +++ b/Nicegram/NGEnv/Sources/NGEnv.swift @@ -4,24 +4,15 @@ import BuildConfig public struct NGEnvObj: Decodable { public let app_review_login_code: String public let app_review_login_phone: String - public let bundle_id: String public let premium_bundle: String public let ng_api_url: String - public let validator_url: String - public let ng_lab_url: String - public let ng_lab_token: String public let moby_key: String - public let app_id: String public let privacy_url: String public let terms_url: String - public let restore_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 google_client_id: String - public let ecommpay_merchant_id: String - public let ecommpay_project_id: Int public let referral_bot: String public let remote_config_cache_duration_seconds: Double public let telegram_auth_bot: String diff --git a/Nicegram/NGOnboarding/BUILD b/Nicegram/NGOnboarding/BUILD index 57c3aba1313..fb34fe7039c 100644 --- a/Nicegram/NGOnboarding/BUILD +++ b/Nicegram/NGOnboarding/BUILD @@ -10,8 +10,8 @@ swift_library( "//submodules/UIKitRuntimeUtils:UIKitRuntimeUtils", "//Nicegram/NGData:NGData", "//Nicegram/NGStrings:NGStrings", + "@swiftpkg_nicegram_assistant_ios//:Sources_FeatPremiumUI", "@swiftpkg_nicegram_assistant_ios//:Sources_NGAiChat", - "@swiftpkg_nicegram_assistant_ios//:Sources_NGPremiumUI", ], visibility = [ "//visibility:public", diff --git a/Nicegram/NGOnboarding/Sources/OnboardingFlow.swift b/Nicegram/NGOnboarding/Sources/OnboardingFlow.swift index c3c233cfc02..98bf9571531 100644 --- a/Nicegram/NGOnboarding/Sources/OnboardingFlow.swift +++ b/Nicegram/NGOnboarding/Sources/OnboardingFlow.swift @@ -1,8 +1,8 @@ +import FeatPremiumUI import Foundation import UIKit import NGAiChat import NGData -import NGPremiumUI import NGStrings public func onboardingController(languageCode: String, onComplete: @escaping () -> Void) -> UIViewController { diff --git a/Nicegram/NGUI/Sources/NicegramSettingsController.swift b/Nicegram/NGUI/Sources/NicegramSettingsController.swift index 8361d219081..dff2a144593 100644 --- a/Nicegram/NGUI/Sources/NicegramSettingsController.swift +++ b/Nicegram/NGUI/Sources/NicegramSettingsController.swift @@ -122,7 +122,6 @@ private enum NicegramSettingsControllerEntry: ItemListNodeEntry { case Account(String) case doubleBottom(String) - case restorePremium(String, String) case unblockHeader(String) case unblock(String, URL) @@ -150,7 +149,7 @@ private enum NicegramSettingsControllerEntry: ItemListNodeEntry { return NicegramSettingsControllerSection.QuickReplies.rawValue case .unblockHeader, .unblock: return NicegramSettingsControllerSection.Unblock.rawValue - case .Account, .restorePremium, .doubleBottom: + case .Account, .doubleBottom: return NicegramSettingsControllerSection.Account.rawValue case .secretMenu: return NicegramSettingsControllerSection.SecretMenu.rawValue @@ -231,9 +230,6 @@ private enum NicegramSettingsControllerEntry: ItemListNodeEntry { case .Account: return 2500 - case .restorePremium: - return 2600 - case .doubleBottom: return 2700 @@ -372,12 +368,6 @@ private enum NicegramSettingsControllerEntry: ItemListNodeEntry { } else { return false } - case let .restorePremium(lhsText, lhsId): - if case let .restorePremium(rhsText, rhsId) = rhs, lhsText == rhsText, lhsId == rhsId { - return true - } else { - return false - } case let .doubleBottom(lhsText): if case let .doubleBottom(rhsText) = rhs, lhsText == rhsText { return true @@ -557,72 +547,6 @@ private enum NicegramSettingsControllerEntry: ItemListNodeEntry { } case let .Account(text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: section) - case let .restorePremium(text, id): - return ItemListActionItem(presentationData: presentationData, title: text, kind: .neutral, alignment: .natural, sectionId: section, style: .blocks) { - SharedLoadingView.start() - guard var urlComponents = URLComponents(string: NGENV.restore_url) else { return } - urlComponents.queryItems = [ - URLQueryItem(name: "id", value: id) - ] - guard let url = urlComponents.url else { return } - var request = URLRequest(url: url) - request.httpMethod = "GET" - let task = URLSession.shared.dataTask(with: request) { (data, response, error) in - var alertTitle = "" - var alertText = "" - guard - let data = data, // is there data - let response = response as? HTTPURLResponse, // is there HTTP response - 200 ..< 300 ~= response.statusCode, // is statusCode 2XX - error == nil // was there no error - else { - SharedLoadingView.stop() - DispatchQueue.main.async { - let controller = standardTextAlertController( - theme: AlertControllerTheme(presentationData: arguments.context.sharedContext.currentPresentationData.with { $0 }), - title: l("TelegramPremium.Failure.Title", locale), - text: l("TelegramPremium.Failure.Description", locale), - actions: [ - TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: {}) - ] - ) - arguments.presentController(controller, nil) - } - return - } - let responseObject = (try? JSONSerialization.jsonObject(with: data)) as? [String: Any] - - SharedLoadingView.stop() - - if let premiumData = responseObject?["data"] as? [String: Any], let premiumAccess = premiumData["premiumAccess"] as? Bool { - if premiumAccess { - AppCache.hasUnlimPremium = true - alertTitle = l("TelegramPremium.Success.Title", locale) - alertText = l("TelegramPremium.Success.Description", locale) - } else { - AppCache.hasUnlimPremium = false - alertTitle = l("TelegramPremium.Failure.Title", locale) - alertText = l("TelegramPremium.Failure.Pending", locale) - } - } else { - alertTitle = l("TelegramPremium.Failure.Title", locale) - alertText = l("TelegramPremium.Failure.Description", locale) - } - - DispatchQueue.main.async { - let controller = standardTextAlertController( - theme: AlertControllerTheme(presentationData: arguments.context.sharedContext.currentPresentationData.with { $0 }), - title: alertTitle, - text: alertText, - actions: [ - TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: {}) - ] - ) - arguments.presentController(controller, nil) - } - } - task.resume() - } case let .doubleBottom(text): return ItemListActionItem(presentationData: presentationData, title: text, kind: .neutral, alignment: .natural, sectionId: section, style: .blocks) { arguments.pushController(doubleBottomListController(context: arguments.context, presentationData: arguments.context.sharedContext.currentPresentationData.with { $0 }, accountsContexts: arguments.accountsContexts)) @@ -792,7 +716,6 @@ private func nicegramSettingsControllerEntries(presentationData: PresentationDat entries.append(.Account(l("NiceFeatures.Account.Header", locale))) - entries.append(.restorePremium(l("TelegramPremium.Title", locale), "\(context.account.peerId.id._internalGetInt64Value())")) if !context.account.isHidden || !VarSystemNGSettings.inDoubleBottom { entries.append(.doubleBottom(l("DoubleBottom.Title", locale))) } diff --git a/Nicegram/NGUI/Sources/PremiumIntroController.swift b/Nicegram/NGUI/Sources/PremiumIntroController.swift deleted file mode 100644 index 25bca2081b5..00000000000 --- a/Nicegram/NGUI/Sources/PremiumIntroController.swift +++ /dev/null @@ -1,915 +0,0 @@ -//import Foundation -//import UIKit -//import Display -//import AsyncDisplayKit -//import SwiftSignalKit -//import TelegramCore -//import TelegramPresentationData -//import DeviceAccess -//import AccountContext -//import NGData -//import NGStrings -//import NGIAP -//import UndoUI -// -//public final class PremiumIntroController : ViewController { -// private let context: AccountContext -// private let splitTest: PremiumIntroUISplitTest? -// private var state: PremiumIntroControllerContent? -// private var splashScreen = false -// -// private var controllerNode: PremiumIntroControllerNode { -// return self.displayNode as! PremiumIntroControllerNode -// } -// -// private var didPlayPresentationAnimation = false -// -// private var presentationData: PresentationData -// private var presentationDataDisposable: Disposable? -// -// private var allow: (() -> Void)? -// private var skip: (() -> Void)? -// public var proceed: ((Bool) -> Void)? -// -// public init(context: AccountContext, splashScreen: Bool = true, splitTest: PremiumIntroUISplitTest? = nil) { -// self.context = context -// self.splitTest = splitTest -// self.presentationData = context.sharedContext.currentPresentationData.with { $0 } -// self.splashScreen = splashScreen -// -// let navigationBarPresentationData: NavigationBarPresentationData -// if splashScreen { -// navigationBarPresentationData = NavigationBarPresentationData(theme: NavigationBarTheme(buttonColor: self.presentationData.theme.rootController.navigationBar.accentTextColor, disabledButtonColor: self.presentationData.theme.rootController.navigationBar.disabledButtonColor, primaryTextColor: self.presentationData.theme.rootController.navigationBar.primaryTextColor, backgroundColor: .clear, enableBackgroundBlur: false, separatorColor: .clear, badgeBackgroundColor: .clear, badgeStrokeColor: .clear, badgeTextColor: .clear), strings: NavigationBarStrings(presentationStrings: self.presentationData.strings)) -// } else { -// navigationBarPresentationData = NavigationBarPresentationData(presentationData: self.presentationData) -// } -// -// super.init(navigationBarPresentationData: navigationBarPresentationData) -// -// self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait) -// -// self.updateThemeAndStrings() -// -// self.presentationDataDisposable = (context.sharedContext.presentationData -// |> deliverOnMainQueue).start(next: { [weak self] presentationData in -// if let strongSelf = self { -// let previousTheme = strongSelf.presentationData.theme -// let previousStrings = strongSelf.presentationData.strings -// -// strongSelf.presentationData = presentationData -// -// if previousTheme !== presentationData.theme || previousStrings !== presentationData.strings { -// strongSelf.updateThemeAndStrings() -// } -// } -// }) -// } -// -// required init(coder aDecoder: NSCoder) { -// fatalError("init(coder:) has not been implemented") -// } -// -// deinit { -// self.presentationDataDisposable?.dispose() -// } -// -// public override func viewDidAppear(_ animated: Bool) { -// super.viewDidAppear(animated) -// -// if let presentationArguments = self.presentationArguments as? ViewControllerPresentationArguments, !self.didPlayPresentationAnimation { -// self.didPlayPresentationAnimation = true -// if case .modalSheet = presentationArguments.presentationAnimation { -// self.controllerNode.animateIn() -// } -// } -// } -// -// private func updateThemeAndStrings() { -// self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style -// -// let navigationBarPresentationData: NavigationBarPresentationData -// if self.splashScreen { -// navigationBarPresentationData = NavigationBarPresentationData(theme: NavigationBarTheme(buttonColor: self.presentationData.theme.rootController.navigationBar.accentTextColor, disabledButtonColor: self.presentationData.theme.rootController.navigationBar.disabledButtonColor, primaryTextColor: self.presentationData.theme.rootController.navigationBar.primaryTextColor, backgroundColor: .clear, enableBackgroundBlur: false, separatorColor: .clear, badgeBackgroundColor: .clear, badgeStrokeColor: .clear, badgeTextColor: .clear), strings: NavigationBarStrings(presentationStrings: self.presentationData.strings)) -// } else { -// navigationBarPresentationData = NavigationBarPresentationData(presentationData: self.presentationData) -// } -// -// self.navigationBar?.updatePresentationData(navigationBarPresentationData) -// self.navigationItem.backBarButtonItem = UIBarButtonItem(title: nil, style: .plain, target: nil, action: nil) -// if self.navigationItem.rightBarButtonItem != nil { -// self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Permissions_Skip, style: .plain, target: self, action: #selector(PremiumIntroController.nextPressed)) -// } -// self.controllerNode.updatePresentationData(self.presentationData) -// } -// -// private func openAppSettings() { -// self.context.sharedContext.applicationBindings.openSettings() -// } -// -// public func setState(_ state: PremiumIntroControllerContent, animated: Bool) { -// guard state != self.state else { -// return -// } -// -// self.state = state -// if case let .permission(permission) = state, let state = permission { -// if case .nearbyLocation = state { -// } else { -// self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Permissions_Skip, style: .plain, target: self, action: #selector(PremiumIntroController.nextPressed)) -// } -// -// switch state { -// case let .contacts(status): -// self.splitTest?.addEvent(.ContactsModalRequest) -// -// self.allow = { [weak self] in -// if let strongSelf = self { -// switch status { -// case .requestable: -// strongSelf.splitTest?.addEvent(.ContactsRequest) -// DeviceAccess.authorizeAccess(to: .contacts, { [weak self] result in -// if let strongSelf = self { -// if result { -// strongSelf.splitTest?.addEvent(.ContactsAllowed) -// } else { -// strongSelf.splitTest?.addEvent(.ContactsDenied) -// } -// strongSelf.proceed?(true) -// } -// }) -// case .denied: -// strongSelf.openAppSettings() -// strongSelf.proceed?(true) -// default: -// break -// } -// } -// } -// case let .notifications(status): -// self.splitTest?.addEvent(.NotificationsModalRequest) -// -// self.allow = { [weak self] in -// if let strongSelf = self { -// switch status { -// case .requestable: -// strongSelf.splitTest?.addEvent(.NotificationsRequest) -// let context = strongSelf.context -// DeviceAccess.authorizeAccess(to: .notifications, registerForNotifications: { [weak context] result in -// context?.sharedContext.applicationBindings.registerForNotifications(result) -// }, { [weak self] result in -// if let strongSelf = self { -// if result { -// strongSelf.splitTest?.addEvent(.NotificationsAllowed) -// } else { -// strongSelf.splitTest?.addEvent(.NotificationsDenied) -// } -// strongSelf.proceed?(true) -// } -// }) -// case .denied, .unreachable: -// strongSelf.openAppSettings() -// strongSelf.proceed?(true) -// default: -// break -// } -// } -// } -// case .siri: -// self.allow = { [weak self] in -// self?.proceed?(true) -// } -// case .cellularData: -// self.allow = { [weak self] in -// if let strongSelf = self { -// strongSelf.openAppSettings() -// strongSelf.proceed?(true) -// } -// } -// case let .nearbyLocation(status): -// self.title = self.presentationData.strings.Permissions_PeopleNearbyTitle_v0 -// -// self.allow = { [weak self] in -// if let strongSelf = self { -// switch status { -// case .requestable: -// DeviceAccess.authorizeAccess(to: .location(.tracking), presentationData: strongSelf.context.sharedContext.currentPresentationData.with { $0 }, { [weak self] result in -// self?.proceed?(result) -// }) -// case .denied, .unreachable: -// strongSelf.openAppSettings() -// strongSelf.proceed?(false) -// default: -// break -// } -// } -// } -// } -// } else { -// self.allow = { [weak self] in -// if let strongSelf = self { -// strongSelf.proceed?(true) -// } -// } -// } -// -// self.skip = { [weak self] in -// self?.proceed?(false) -// } -// self.controllerNode.setState(state, transition: animated ? .animated(duration: 0.4, curve: .spring) : .immediate) -// } -// -// public override func loadDisplayNode() { -// self.displayNode = PremiumIntroControllerNode(context: self.context, splitTest: self.splitTest) -// self.displayNodeDidLoad() -// -// self.controllerNode.allow = { [weak self] in -// self?.allow?() -// } -// self.controllerNode.openPrivacyPolicy = { [weak self] in -// if let strongSelf = self { -// let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } -// let locale = presentationData.strings.baseLanguageCode -// strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .info(text: l("IAP.Common.Connecting", locale)), elevatedLayout: false, animateInAsReplacement: true, action: { _ in -// return false -// }), in: .current) -// } -// let observer = NotificationCenter.default.addObserver(forName: .IAPHelperPurchaseNotification, object: nil, queue: .main, using: { notification in -// let productID = notification.object as? String -// if productID == NicegramProducts.Premium { -// // NGSettings.premium = true -// validatePremium(isPremium(), forceValid: true) -// if (isPremium()) { -// if let strongSelf = self { -// let c = premiumController(context: strongSelf.context) -// -// (strongSelf.navigationController as? NavigationController)?.replaceAllButRootController(c, animated: true) -// } -// } else { -// if let strongSelf = self { -// let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } -// let c = getIAPErrorController(context: strongSelf.context, "IAP.Common.ValidateError", presentationData) -// strongSelf.present(c, in: .window(.root)) -// } -// } -// } -// }) -// NicegramProducts.store.restorePurchases() -// } -// } -// -// public override func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { -// super.containerLayoutUpdated(layout, transition: transition) -// -// self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.splashScreen ? 0.0 : self.navigationLayout(layout: layout).navigationFrame.maxY, transition: transition) -// } -// -// @objc private func nextPressed() { -// self.skip?() -// } -// -// override public func dismiss(completion: (() -> Void)? = nil) { -// self.controllerNode.animateOut(completion: { [weak self] in -// self?.presentingViewController?.dismiss(animated: false, completion: nil) -// completion?() -// }) -// } -//} -// -// -//// MARK: CONTROLLER NODE -//import Foundation -//import UIKit -//import Display -//import AsyncDisplayKit -//import SwiftSignalKit -//import TelegramCore -//import TelegramPresentationData -//import AccountContext -//import TelegramPermissions -// -//public struct PremiumIntroControllerCustomIcon: Equatable { -// let light: UIImage? -// let dark: UIImage? -// -// public init(light: UIImage?, dark: UIImage?) { -// self.light = light -// self.dark = dark -// } -//} -// -//public enum PremiumIntroControllerContent: Equatable { -// case permission(PermissionState?) -// case custom(icon: PremiumIntroControllerCustomIcon, title: String, subtitle: String?, text: String, buttonTitle: String, footerText: String?) -//} -// -//private struct PremiumIntroControllerDataState: Equatable { -// var state: PremiumIntroControllerContent? -//} -// -//private struct PremiumIntroControllerLayoutState: Equatable { -// let layout: ContainerViewLayout -// let navigationHeight: CGFloat -//} -// -//private struct PremiumIntroControllerInnerState: Equatable { -// var layout: PremiumIntroControllerLayoutState? -// var data: PremiumIntroControllerDataState -//} -// -//private struct PremiumIntroControllerState: Equatable { -// var layout: PremiumIntroControllerLayoutState -// var data: PremiumIntroControllerDataState -//} -// -//extension PremiumIntroControllerState { -// init?(_ state: PremiumIntroControllerInnerState) { -// guard let layout = state.layout else { -// return nil -// } -// self.init(layout: layout, data: state.data) -// } -//} -// -//private func localizedString(for key: String, strings: PresentationStrings, fallback: String = "") -> String { -// if let string = strings.primaryComponent.dict[key] { -// return string -// } else if let string = strings.secondaryComponent?.dict[key] { -// return string -// } else { -// return fallback -// } -//} -// -//final class PremiumIntroControllerNode: ASDisplayNode { -// private let context: AccountContext -// private var presentationData: PresentationData -// private let splitTest: PremiumIntroUISplitTest? -// -// private var innerState: PremiumIntroControllerInnerState -// -// private var contentNode: PremiumIntroContentNode? -// -// var allow: (() -> Void)? -// var openPrivacyPolicy: (() -> Void)? -// var dismiss: (() -> Void)? -// -// init(context: AccountContext, splitTest: PremiumIntroUISplitTest?) { -// self.context = context -// self.presentationData = context.sharedContext.currentPresentationData.with { $0 } -// self.splitTest = splitTest -// self.innerState = PremiumIntroControllerInnerState(layout: nil, data: PremiumIntroControllerDataState(state: nil)) -// -// super.init() -// -// self.setViewBlock({ -// return UITracingLayerView() -// }) -// -// self.updatePresentationData(self.presentationData) -// } -// -// func updatePresentationData(_ presentationData: PresentationData) { -// self.presentationData = presentationData -// -// self.backgroundColor = self.presentationData.theme.list.plainBackgroundColor -// self.contentNode?.updatePresentationData(self.presentationData) -// } -// -// func animateIn(completion: (() -> Void)? = nil) { -// self.layer.animatePosition(from: CGPoint(x: self.layer.position.x, y: self.layer.position.y + self.layer.bounds.size.height), to: self.layer.position, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring) -// } -// -// func animateOut(completion: (() -> Void)? = nil) { -// self.layer.animatePosition(from: self.layer.position, to: CGPoint(x: self.layer.position.x, y: self.layer.position.y + self.layer.bounds.size.height), duration: 0.2, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, completion: { _ in -// completion?() -// }) -// } -// -// public func setState(_ state: PremiumIntroControllerContent, transition: ContainedViewLayoutTransition) { -// self.updateState({ currentState -> PremiumIntroControllerInnerState in -// return PremiumIntroControllerInnerState(layout: currentState.layout, data: PremiumIntroControllerDataState(state: state)) -// }, transition: transition) -// } -// -// private func updateState(_ f: (PremiumIntroControllerInnerState) -> PremiumIntroControllerInnerState, transition: ContainedViewLayoutTransition) { -// let updatedState = f(self.innerState) -// if updatedState != self.innerState { -// self.innerState = updatedState -// if let state = PremiumIntroControllerState(updatedState) { -// self.transition(state: state, transition: transition) -// } -// } -// } -// -// private func transition(state: PremiumIntroControllerState, transition: ContainedViewLayoutTransition) { -// let insets = state.layout.layout.insets(options: [.statusBar]) -// let contentFrame = CGRect(origin: CGPoint(x: 0.0, y: state.layout.navigationHeight), size: CGSize(width: state.layout.layout.size.width, height: state.layout.layout.size.height)) -// -// if let state = state.data.state { -// switch state { -// case let .permission(permission): -// if permission?.kind.rawValue != self.contentNode?.kind { -// if let dataState = permission { -// let icon: UIImage? -// let title: String -// let text: String -// let buttonTitle: String -// let hasPrivacyPolicy: Bool -// -// switch dataState { -// case let .contacts(status): -// icon = UIImage(bundleImageName: "Settings/Permissions/Contacts") -// if let splitTest = self.splitTest, case let .modal(titleKey, textKey, allowTitleKey, allowInSettingsTitleKey) = splitTest.configuration.contacts { -// title = localizedString(for: titleKey, strings: self.presentationData.strings) -// text = localizedString(for: textKey, strings: self.presentationData.strings) -// if status == .denied { -// buttonTitle = localizedString(for: allowInSettingsTitleKey, strings: self.presentationData.strings) -// } else { -// buttonTitle = localizedString(for: allowTitleKey, strings: self.presentationData.strings) -// } -// } else { -// title = self.presentationData.strings.Permissions_ContactsTitle_v0 -// text = self.presentationData.strings.Permissions_ContactsText_v0 -// if status == .denied { -// buttonTitle = self.presentationData.strings.Permissions_ContactsAllowInSettings_v0 -// } else { -// buttonTitle = self.presentationData.strings.Permissions_ContactsAllow_v0 -// } -// } -// hasPrivacyPolicy = true -// case let .notifications(status): -// icon = UIImage(bundleImageName: "Settings/Permissions/Notifications") -// if let splitTest = self.splitTest, case let .modal(titleKey, textKey, allowTitleKey, allowInSettingsTitleKey) = splitTest.configuration.notifications { -// title = localizedString(for: titleKey, strings: self.presentationData.strings, fallback: self.presentationData.strings.Permissions_NotificationsTitle_v0) -// text = localizedString(for: textKey, strings: self.presentationData.strings, fallback: self.presentationData.strings.Permissions_NotificationsText_v0) -// if status == .denied { -// buttonTitle = localizedString(for: allowInSettingsTitleKey, strings: self.presentationData.strings, fallback: self.presentationData.strings.Permissions_NotificationsAllowInSettings_v0) -// } else { -// buttonTitle = localizedString(for: allowTitleKey, strings: self.presentationData.strings, fallback: self.presentationData.strings.Permissions_NotificationsAllow_v0) -// } -// } else { -// title = self.presentationData.strings.Permissions_NotificationsTitle_v0 -// text = self.presentationData.strings.Permissions_NotificationsText_v0 -// if status == .denied { -// buttonTitle = self.presentationData.strings.Permissions_NotificationsAllowInSettings_v0 -// } else { -// buttonTitle = self.presentationData.strings.Permissions_NotificationsAllow_v0 -// } -// } -// hasPrivacyPolicy = false -// case let .siri(status): -// icon = UIImage(bundleImageName: "Settings/Permissions/Siri") -// title = self.presentationData.strings.Permissions_SiriTitle_v0 -// text = self.presentationData.strings.Permissions_SiriText_v0 -// if status == .denied { -// buttonTitle = self.presentationData.strings.Permissions_SiriAllowInSettings_v0 -// } else { -// buttonTitle = self.presentationData.strings.Permissions_SiriAllow_v0 -// } -// hasPrivacyPolicy = false -// case .cellularData: -// icon = UIImage(bundleImageName: "Settings/Permissions/CellularData") -// title = self.presentationData.strings.Permissions_CellularDataTitle_v0 -// text = self.presentationData.strings.Permissions_CellularDataText_v0 -// buttonTitle = self.presentationData.strings.Permissions_CellularDataAllowInSettings_v0 -// hasPrivacyPolicy = false -// case let .nearbyLocation(status): -// icon = nil -// title = self.presentationData.strings.Permissions_PeopleNearbyTitle_v0 -// text = self.presentationData.strings.Permissions_PeopleNearbyText_v0 -// if status == .denied { -// buttonTitle = self.presentationData.strings.Permissions_PeopleNearbyAllowInSettings_v0 -// } else { -// buttonTitle = self.presentationData.strings.Permissions_PeopleNearbyAllow_v0 -// } -// hasPrivacyPolicy = false -// } -// -// let contentNode = PremiumIntroContentNode(theme: self.presentationData.theme, strings: self.presentationData.strings, kind: dataState.kind.rawValue, icon: .image(icon), title: title, text: text, buttonTitle: buttonTitle, buttonAction: { [weak self] in -// self?.allow?() -// }, openPrivacyPolicy: hasPrivacyPolicy ? self.openPrivacyPolicy : nil) -// self.insertSubnode(contentNode, at: 0) -// contentNode.updateLayout(size: contentFrame.size, insets: insets, transition: .immediate) -// contentNode.frame = contentFrame -// if let currentContentNode = self.contentNode { -// transition.updatePosition(node: currentContentNode, position: CGPoint(x: -contentFrame.size.width / 2.0, y: contentFrame.midY), completion: { [weak currentContentNode] _ in -// currentContentNode?.removeFromSupernode() -// }) -// transition.animateHorizontalOffsetAdditive(node: contentNode, offset: -contentFrame.width) -// } else if transition.isAnimated { -// contentNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) -// } -// self.contentNode = contentNode -// } else if let currentContentNode = self.contentNode { -// transition.updateAlpha(node: currentContentNode, alpha: 0.0, completion: { [weak currentContentNode] _ in -// currentContentNode?.removeFromSupernode() -// }) -// self.contentNode = nil -// } -// } else if let contentNode = self.contentNode { -// transition.updateFrame(node: contentNode, frame: contentFrame) -// contentNode.updateLayout(size: contentFrame.size, insets: insets, transition: transition) -// } -// case let .custom(icon, title, subtitle, text, buttonTitle, footerText): -// if let contentNode = self.contentNode { -// transition.updateFrame(node: contentNode, frame: contentFrame) -// contentNode.updateLayout(size: contentFrame.size, insets: insets, transition: transition) -// } else { -// let contentNode = PremiumIntroContentNode(theme: self.presentationData.theme, strings: self.presentationData.strings, kind: 0, icon: .icon(icon), title: title, subtitle: subtitle, text: text, buttonTitle: buttonTitle, footerText: footerText, buttonAction: { [weak self] in -// self?.allow?() -// }, openPrivacyPolicy: self.openPrivacyPolicy) -// self.insertSubnode(contentNode, at: 0) -// contentNode.updateLayout(size: contentFrame.size, insets: insets, transition: .immediate) -// contentNode.frame = contentFrame -// self.contentNode = contentNode -// } -// } -// } -// } -// -// func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) { -// self.updateState({ state in -// var state = state -// state.layout = PremiumIntroControllerLayoutState(layout: layout, navigationHeight: navigationBarHeight) -// return state -// }, transition: transition) -// } -// -// @objc func privacyPolicyPressed() { -// self.openPrivacyPolicy?() -// } -//} -// -// -//// MARK: Split TEST -// -//import Foundation -//import UIKit -//import SwiftSignalKit -//import Postbox -//import TelegramCore -//import TelegramPermissions -// -//extension PermissionKind { -// fileprivate static var defaultOrder: [PermissionKind] { -// return [.contacts, .notifications] -// } -//} -// -//public enum PremiumIntroUIRequestVariation { -// case `default` -// case modal(title: String, text: String, allowTitle: String, allowInSettingsTitle: String) -//} -// -//public struct PremiumIntroUISplitTest: SplitTest { -// public typealias Configuration = PremiumIntroUIConfiguration -// public typealias Event = PremiumIntroUIEvent -// -// public let postbox: Postbox -// public let bucket: String? -// public let configuration: Configuration -// -// public init(postbox: Postbox, bucket: String?, configuration: Configuration) { -// self.postbox = postbox -// self.bucket = bucket -// self.configuration = configuration -// } -// -// public struct PremiumIntroUIConfiguration: SplitTestConfiguration { -// public static var defaultValue: PremiumIntroUIConfiguration { -// return PremiumIntroUIConfiguration(contacts: .default, notifications: .default, order: PermissionKind.defaultOrder) -// } -// -// public let contacts: PremiumIntroUIRequestVariation -// public let notifications: PremiumIntroUIRequestVariation -// public let order: [PermissionKind] -// -// fileprivate init(contacts: PremiumIntroUIRequestVariation, notifications: PremiumIntroUIRequestVariation, order: [PermissionKind]) { -// self.contacts = contacts -// self.notifications = notifications -// self.order = order -// } -// -// static func with(appConfiguration: AppConfiguration) -> (PremiumIntroUIConfiguration, String?) { -// if let data = appConfiguration.data, let permissions = data["ui_permissions_modals"] as? [String: Any] { -// let contacts: PremiumIntroUIRequestVariation -// if let modal = permissions["phonebook_modal"] as? [String: Any] { -// contacts = .modal(title: modal["popup_title_lang"] as? String ?? "", text: modal["popup_text_lang"] as? String ?? "", allowTitle: modal["popup_allowbtn_lang"] as? String ?? "", allowInSettingsTitle: modal["popup_allowbtn_settings_lang"] as? String ?? "") -// } else { -// contacts = .default -// } -// -// let notifications: PremiumIntroUIRequestVariation -// if let modal = permissions["notifications_modal"] as? [String: Any] { -// notifications = .modal(title: modal["popup_title_lang"] as? String ?? "", text: modal["popup_text_lang"] as? String ?? "", allowTitle: modal["popup_allowbtn_lang"] as? String ?? "", allowInSettingsTitle: modal["popup_allowbtn_settings_lang"] as? String ?? "") -// } else { -// notifications = .default -// } -// -// let order: [PermissionKind] -// if let values = permissions["order"] as? [String] { -// order = values.compactMap { value in -// switch value { -// case "phonebook": -// return .contacts -// case "notifications": -// return .notifications -// default: -// return nil -// } -// } -// } else { -// order = PermissionKind.defaultOrder -// } -// -// return (PremiumIntroUIConfiguration(contacts: contacts, notifications: notifications, order: order), permissions["bucket"] as? String) -// } else { -// return (.defaultValue, nil) -// } -// } -// } -// -// public enum PremiumIntroUIEvent: String, SplitTestEvent { -// case ContactsModalRequest = "phbmodal_request" -// case ContactsRequest = "phbperm_request" -// case ContactsAllowed = "phbperm_allow" -// case ContactsDenied = "phbperm_disallow" -// case NotificationsModalRequest = "ntfmodal_request" -// case NotificationsRequest = "ntfperm_request" -// case NotificationsAllowed = "ntfperm_allow" -// case NotificationsDenied = "ntfperm_disallow" -// } -//} -// -//public func premiumIntroUISplitTest(postbox: Postbox) -> Signal { -// return postbox.preferencesView(keys: [PreferencesKeys.appConfiguration]) -// |> mapToSignal { view -> Signal in -// let appConfiguration = view.values[PreferencesKeys.appConfiguration]?.get(AppConfiguration.self) ?? AppConfiguration.defaultValue -// if appConfiguration.data != nil { -// let (config, bucket) = PremiumIntroUISplitTest.Configuration.with(appConfiguration: appConfiguration) -// return .single(PremiumIntroUISplitTest(postbox: postbox, bucket: bucket, configuration: config)) -// } else { -// return .never() -// } -// } |> take(1) -//} -// -// -//// MARK: CONTENT NODE -// -//import Foundation -//import UIKit -//import Display -//import AsyncDisplayKit -//import TelegramPresentationData -//import TextFormat -//import TelegramPermissions -//import PeersNearbyIconNode -//import SolidRoundedButtonNode -//import Markdown -//import NGData -//import NGStrings -// -//public enum PremiumIntroContentIcon { -// case image(UIImage?) -// case icon(PremiumIntroControllerCustomIcon) -// -// public func imageForTheme(_ theme: PresentationTheme) -> UIImage? { -// switch self { -// case let .image(image): -// return image -// case let .icon(icon): -// return theme.overallDarkAppearance ? (icon.dark ?? icon.light) : icon.light -// } -// } -//} -// -//public final class PremiumIntroContentNode: ASDisplayNode { -// private var theme: PresentationTheme -// public let kind: Int32 -// -// private let iconNode: ASImageNode -// private let nearbyIconNode: PeersNearbyIconNode? -// private let titleNode: ImmediateTextNode -// private let subtitleNode: ImmediateTextNode -// private let textNode: ImmediateTextNode -// private let actionButton: SolidRoundedButtonNode -// private let footerNode: ImmediateTextNode -// private let privacyPolicyButton: HighlightableButtonNode -// -// private let icon: PremiumIntroContentIcon -// private var title: String -// private var text: String -// -// public var buttonAction: (() -> Void)? -// public var openPrivacyPolicy: (() -> Void)? -// -// public var validLayout: (CGSize, UIEdgeInsets)? -// -// public init(theme: PresentationTheme, strings: PresentationStrings, kind: Int32, icon: PremiumIntroContentIcon, title: String, subtitle: String? = nil, text: String, buttonTitle: String, footerText: String? = nil, buttonAction: @escaping () -> Void, openPrivacyPolicy: (() -> Void)?) { -// self.theme = theme -// self.kind = kind -// -// self.buttonAction = buttonAction -// self.openPrivacyPolicy = openPrivacyPolicy -// -// self.icon = icon -// self.title = title -// self.text = text -// -// self.iconNode = ASImageNode() -// self.iconNode.isLayerBacked = true -// self.iconNode.displayWithoutProcessing = true -// self.iconNode.displaysAsynchronously = false -// -// if kind == PermissionKind.nearbyLocation.rawValue { -// self.nearbyIconNode = PeersNearbyIconNode(theme: theme) -// } else { -// self.nearbyIconNode = nil -// } -// -// self.titleNode = ImmediateTextNode() -// self.titleNode.maximumNumberOfLines = 0 -// self.titleNode.textAlignment = .center -// self.titleNode.isUserInteractionEnabled = false -// self.titleNode.displaysAsynchronously = false -// -// self.subtitleNode = ImmediateTextNode() -// self.subtitleNode.maximumNumberOfLines = 1 -// self.subtitleNode.textAlignment = .center -// self.subtitleNode.isUserInteractionEnabled = false -// self.subtitleNode.displaysAsynchronously = false -// -// self.textNode = ImmediateTextNode() -// self.textNode.textAlignment = .center -// self.textNode.maximumNumberOfLines = 0 -// self.textNode.displaysAsynchronously = false -// -// self.actionButton = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(theme: theme), height: 48.0, cornerRadius: 9.0, gloss: true) -// -// self.footerNode = ImmediateTextNode() -// self.footerNode.textAlignment = .center -// self.footerNode.maximumNumberOfLines = 0 -// self.footerNode.displaysAsynchronously = false -// -// self.privacyPolicyButton = HighlightableButtonNode() -// self.privacyPolicyButton.setTitle(l("IAP.Common.Restore", strings.baseLanguageCode), with: Font.regular(16.0), with: theme.list.itemAccentColor, for: .normal) -// -// super.init() -// -// self.iconNode.image = icon.imageForTheme(theme) -// self.title = title -// -// let body = MarkdownAttributeSet(font: Font.regular(16.0), textColor: theme.list.itemPrimaryTextColor) -// let link = MarkdownAttributeSet(font: Font.regular(16.0), textColor: theme.list.itemAccentColor, additionalAttributes: [TelegramTextAttributes.URL: ""]) -// self.textNode.attributedText = parseMarkdownIntoAttributedString(text.replacingOccurrences(of: "]", with: "]()"), attributes: MarkdownAttributes(body: body, bold: body, link: link, linkAttribute: { _ in nil }), textAlignment: .center) -// -// self.actionButton.title = buttonTitle -// self.privacyPolicyButton.isHidden = false -// -// if let subtitle = subtitle { -// self.subtitleNode.attributedText = NSAttributedString(string: subtitle, font: Font.regular(13.0), textColor: theme.list.freeTextColor, paragraphAlignment: .center) -// } -// -// if let footerText = footerText { -// self.footerNode.attributedText = NSAttributedString(string: footerText, font: Font.regular(13.0), textColor: theme.list.freeTextColor, paragraphAlignment: .center) -// } -// -// self.addSubnode(self.iconNode) -// if let nearbyIconNode = self.nearbyIconNode { -// self.addSubnode(nearbyIconNode) -// } -// self.addSubnode(self.titleNode) -// self.addSubnode(self.subtitleNode) -// self.addSubnode(self.textNode) -// self.addSubnode(self.actionButton) -// self.addSubnode(self.footerNode) -// self.addSubnode(self.privacyPolicyButton) -// -// self.actionButton.pressed = { [weak self] in -// self?.buttonAction?() -// } -// -// self.privacyPolicyButton.addTarget(self, action: #selector(self.privacyPolicyPressed), forControlEvents: .touchUpInside) -// } -// -// public func updatePresentationData(_ presentationData: PresentationData) { -// let theme = presentationData.theme -// self.theme = theme -// -// self.iconNode.image = self.icon.imageForTheme(theme) -// -// let body = MarkdownAttributeSet(font: Font.regular(16.0), textColor: theme.list.itemPrimaryTextColor) -// let link = MarkdownAttributeSet(font: Font.regular(16.0), textColor: theme.list.itemAccentColor, additionalAttributes: [TelegramTextAttributes.URL: ""]) -// self.textNode.attributedText = parseMarkdownIntoAttributedString(self.text.replacingOccurrences(of: "]", with: "]()"), attributes: MarkdownAttributes(body: body, bold: body, link: link, linkAttribute: { _ in nil }), textAlignment: .center) -// -// if let subtitle = self.subtitleNode.attributedText?.string { -// self.subtitleNode.attributedText = NSAttributedString(string: subtitle, font: Font.regular(13.0), textColor: theme.list.freeTextColor, paragraphAlignment: .center) -// } -// if let footerText = self.footerNode.attributedText?.string { -// self.footerNode.attributedText = NSAttributedString(string: footerText, font: Font.regular(13.0), textColor: theme.list.freeTextColor, paragraphAlignment: .center) -// } -// -// if let privacyPolicyTitle = self.privacyPolicyButton.attributedTitle(for: .normal)?.string { -// self.privacyPolicyButton.setTitle(privacyPolicyTitle, with: Font.regular(16.0), with: theme.list.itemAccentColor, for: .normal) -// } -// -// if let validLayout = self.validLayout { -// self.updateLayout(size: validLayout.0, insets: validLayout.1, transition: .immediate) -// } -// } -// -// @objc private func privacyPolicyPressed() { -// self.openPrivacyPolicy?() -// } -// -// public func updateLayout(size: CGSize, insets: UIEdgeInsets, transition: ContainedViewLayoutTransition) { -// self.validLayout = (size, insets) -// -// let sidePadding: CGFloat -// let fontSize: CGFloat -// if min(size.width, size.height) > 330.0 { -// fontSize = 24.0 -// sidePadding = 36.0 -// } else { -// fontSize = 20.0 -// sidePadding = 20.0 -// } -// -// let smallerSidePadding: CGFloat = 20.0 -// -// self.titleNode.attributedText = NSAttributedString(string: self.title, font: Font.bold(fontSize), textColor: self.theme.list.itemPrimaryTextColor) -// -// let titleSize = self.titleNode.updateLayout(CGSize(width: size.width - sidePadding * 2.0, height: .greatestFiniteMagnitude)) -// let subtitleSize = self.subtitleNode.updateLayout(CGSize(width: size.width - smallerSidePadding * 2.0, height: .greatestFiniteMagnitude)) -// let textSize = self.textNode.updateLayout(CGSize(width: size.width - sidePadding * 2.0, height: .greatestFiniteMagnitude)) -// let buttonInset: CGFloat = 38.0 -// let buttonWidth = min(size.width, size.height) - buttonInset * 2.0 -// let buttonHeight = self.actionButton.updateLayout(width: buttonWidth, transition: transition) -// let footerSize = self.footerNode.updateLayout(CGSize(width: size.width - smallerSidePadding * 2.0, height: .greatestFiniteMagnitude)) -// let privacyButtonSize = self.privacyPolicyButton.measure(CGSize(width: size.width - sidePadding * 2.0, height: .greatestFiniteMagnitude)) -// -// let availableHeight = floor(size.height - insets.top - insets.bottom - titleSize.height - subtitleSize.height - textSize.height - buttonHeight) -// -// let titleTextSpacing: CGFloat = max(15.0, floor(availableHeight * 0.045)) -// let titleSubtitleSpacing: CGFloat = 6.0 -// let buttonSpacing: CGFloat = max(19.0, floor(availableHeight * 0.075)) -// var contentHeight = titleSize.height + titleTextSpacing + textSize.height + buttonHeight + buttonSpacing -// if subtitleSize.height > 0.0 { -// contentHeight += titleSubtitleSpacing + subtitleSize.height -// } -// -// var imageSize = CGSize() -// var imageSpacing: CGFloat = 0.0 -// if let icon = self.iconNode.image, size.width < size.height { -// imageSpacing = floor(availableHeight * 0.12) -// imageSize = icon.size -// contentHeight += imageSize.height + imageSpacing -// } -// if let _ = self.nearbyIconNode, size.width < size.height { -// imageSpacing = floor(availableHeight * 0.12) -// imageSize = CGSize(width: 120.0, height: 120.0) -// contentHeight += imageSize.height + imageSpacing -// } -// -// let privacySpacing: CGFloat = max(30.0 + privacyButtonSize.height, (availableHeight - titleTextSpacing - buttonSpacing - imageSize.height - imageSpacing) / 2.0) -// -// var verticalOffset: CGFloat = 0.0 -// if size.height >= 568.0 { -// verticalOffset = availableHeight * 0.05 -// } -// -// let contentOrigin = insets.top + floor((size.height - insets.top - insets.bottom - contentHeight) / 2.0) - verticalOffset -// let iconFrame = CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) / 2.0), y: contentOrigin), size: imageSize) -// let nearbyIconFrame = CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) / 2.0), y: contentOrigin), size: imageSize) -// let titleFrame = CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: iconFrame.maxY + imageSpacing), size: titleSize) -// -// let subtitleFrame: CGRect -// if subtitleSize.height > 0.0 { -// subtitleFrame = CGRect(origin: CGPoint(x: floor((size.width - subtitleSize.width) / 2.0), y: titleFrame.maxY + titleSubtitleSpacing), size: subtitleSize) -// } else { -// subtitleFrame = titleFrame -// } -// -// let textFrame = CGRect(origin: CGPoint(x: floor((size.width - textSize.width) / 2.0), y: subtitleFrame.maxY + titleTextSpacing), size: textSize) -// let buttonFrame = CGRect(origin: CGPoint(x: floor((size.width - buttonWidth) / 2.0), y: textFrame.maxY + buttonSpacing), size: CGSize(width: buttonWidth, height: buttonHeight)) -// -// let footerFrame = CGRect(origin: CGPoint(x: floor((size.width - footerSize.width) / 2.0), y: size.height - footerSize.height - insets.bottom - 8.0), size: footerSize) -// -// let privacyButtonFrame = CGRect(origin: CGPoint(x: floor((size.width - privacyButtonSize.width) / 2.0), y: buttonFrame.maxY + floor((privacySpacing - privacyButtonSize.height) / 2.0)), size: privacyButtonSize) -// -// transition.updateFrame(node: self.iconNode, frame: iconFrame) -// if let nearbyIconNode = self.nearbyIconNode { -// transition.updateFrame(node: nearbyIconNode, frame: nearbyIconFrame) -// } -// transition.updateFrame(node: self.titleNode, frame: titleFrame) -// transition.updateFrame(node: self.subtitleNode, frame: subtitleFrame) -// transition.updateFrame(node: self.textNode, frame: textFrame) -// transition.updateFrame(node: self.actionButton, frame: buttonFrame) -// transition.updateFrame(node: self.footerNode, frame: footerFrame) -// transition.updateFrame(node: self.privacyPolicyButton, frame: privacyButtonFrame) -// -// self.footerNode.isHidden = size.height < 568.0 -// } -//} -// diff --git a/Package.resolved b/Package.resolved index e5762526750..f53c29132a7 100644 --- a/Package.resolved +++ b/Package.resolved @@ -14,8 +14,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/scenee/FloatingPanel", "state" : { - "revision" : "dd238884bf85e96a4c6c2703fc2fc1ff0f5c7494", - "version" : "2.8.0" + "revision" : "5b33d3d5ff1f50f4a2d64158ccfe8c07b5a3e649", + "version" : "2.8.1" } }, { @@ -42,7 +42,7 @@ "location" : "git@bitbucket.org:mobyrix/nicegram-assistant-ios.git", "state" : { "branch" : "develop", - "revision" : "74a903bbc75af22ef19aa77ab20ba8df1ae0de27" + "revision" : "c3686ba5c4f4ad5eb0273bc9dfd5784e0971a5a2" } }, { @@ -59,8 +59,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/SDWebImage/SDWebImage.git", "state" : { - "revision" : "fd1950de05a5ad77cb252fd88576c1e1809ee50d", - "version" : "5.18.4" + "revision" : "1b9a2e902cbde5fdf362faa0f4fd76ea74d74305", + "version" : "5.18.5" } }, { diff --git a/Telegram/BUILD b/Telegram/BUILD index a0670469974..d3d1bc57869 100644 --- a/Telegram/BUILD +++ b/Telegram/BUILD @@ -1804,6 +1804,14 @@ ios_extension( ], ) +# MARK: Nicegram +nicegram_queries_schemes = """ +appesim +com.sticker.studio +inlab +plnt +""" + plist_fragment( name = "TelegramInfoPlist", extension = "plist", @@ -1832,6 +1840,7 @@ plist_fragment( LSApplicationQueriesSchemes + {nicegram_queries_schemes} instagram comgooglemaps-x-callback foursquare @@ -1976,6 +1985,7 @@ plist_fragment( UIApplicationSupportsIndirectInputEvents """.format( + nicegram_queries_schemes = nicegram_queries_schemes, telegram_bundle_id = telegram_bundle_id, ) ) diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index d1e40c57d45..0a88c7a78db 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -10215,9 +10215,9 @@ Sorry for the inconvenience."; "ReassignBoost.ExpiresOn" = "Boost expires on %@"; "ReassignBoost.WaitForCooldown" = "Wait until the boost is available or get **%1$@** more boosts by gifting a **Telegram Premium** subscription."; -"ReassignBoost.Success" = "%1$@ are reassigned from %2$@."; -"ReassignBoost.Boosts_1" = "%@ boost"; -"ReassignBoost.Boosts_any" = "%@ boosts"; +"ReassignBoost.Success" = "%1$@ from %2$@."; +"ReassignBoost.Boosts_1" = "%@ boost is reassigned"; +"ReassignBoost.Boosts_any" = "%@ boosts are reassigned"; "ReassignBoost.OtherChannels_1" = "%@ other channel"; "ReassignBoost.OtherChannels_any" = "%@ other channels"; @@ -10297,8 +10297,8 @@ Sorry for the inconvenience."; "Stats.Boosts.Gift" = "Gift"; "Stats.Boosts.TabBoosts_1" = "%@ Boost"; "Stats.Boosts.TabBoosts_any" = "%@ Boosts"; -"Stats.Boosts.TabGifts_1" = "%@ Boost"; -"Stats.Boosts.TabGifts_any" = "%@ Boosts"; +"Stats.Boosts.TabGifts_1" = "%@ Gift"; +"Stats.Boosts.TabGifts_any" = "%@ Gifts"; "Stats.Boosts.ToBeDistributed" = "To Be Distributed"; "Stats.Boosts.Unclaimed" = "Unclaimed"; "Stats.Boosts.GetBoosts" = "Get Boosts via Gifts"; @@ -10364,6 +10364,8 @@ Sorry for the inconvenience."; "Chat.Giveaway.Info.DidntWin" = "You didn't win a prize in this giveaway."; "Chat.Giveaway.Info.ViewPrize" = "View My Prize"; +"Chat.Giveaway.Info.FullDate" = "**%1$@** on **%2$@**"; + "Chat.Giveaway.Toast.NotAllowed" = "You can't participate in this giveaway."; "Chat.Giveaway.Toast.Participating" = "You are participating in this giveaway."; "Chat.Giveaway.Toast.NotQualified" = "You are not qualified for this giveaway yet."; @@ -10414,6 +10416,7 @@ Sorry for the inconvenience."; "ChannelBoost.EnableColors" = "Enable Colors"; "ChannelBoost.EnableColorsText" = "Your channel needs %1$@ to change channel color.\n\nAsk your **Premium** subscribers to boost your channel with this link:"; +"ChannelBoost.EnableColorsLevelText" = "Your channel needs **Level %1$@** to change channel color.\n\nAsk your **Premium** subscribers to boost your channel with this link:"; "ChannelBoost.BoostAgain" = "Boost Again"; "Settings.New" = "NEW"; @@ -10423,3 +10426,41 @@ Sorry for the inconvenience."; "Channel.ChannelColor" = "Channel Color"; "TextFormat.Code" = "Code"; + +"Notification.ChannelJoinedByYou" = "You joined the channel"; + +"CountriesList.SelectCountries" = "Select Countries"; +"CountriesList.SaveCountries" = "Save Countries"; +"CountriesList.SelectUpTo_1" = "select up to %@ country"; +"CountriesList.SelectUpTo_any" = "select up to %@ countries"; +"CountriesList.Search" = "Search"; +"CountriesList.MaximumReached" = "You can select up to %@."; +"CountriesList.MaximumReached.Countries_1" = "%@ country"; +"CountriesList.MaximumReached.Countries_any" = "%@ countries"; + +"Message.GiveawayOngoing" = "Giveaway: %1$@ on %2$@"; +"Message.GiveawayOngoing.Winners_1" = "%@ winner to be selected"; +"Message.GiveawayOngoing.Winners_any" = "%@ winners to be selected"; + +"Message.GiveawayFinished" = "Giveaway: %1$@ on %2$@"; +"Message.GiveawayFinished.Winners_1" = "%@ winner was selected"; +"Message.GiveawayFinished.Winners_any" = "%@ winners were selected"; + +"Message.GiveawayStarted" = "Channel started a giveaway"; +"Message.GiveawayStartedOther" = "%@ started a giveaway"; + +"Conversation.PinnedGiveaway" = "Giveaway"; + +"Conversation.PinnedGiveaway.Ongoing" = "%1$@ on %2$@"; +"Conversation.PinnedGiveaway.Ongoing.Winners_1" = "%@ winner to be selected"; +"Conversation.PinnedGiveaway.Ongoing.Winners_any" = "%@ winners to be selected"; + +"Conversation.PinnedGiveaway.Finished" = "%1$@ on %2$@"; +"Conversation.PinnedGiveaway.Finished.Winners_1" = "%@ winner was selected"; +"Conversation.PinnedGiveaway.Finished.Winners_any" = "%@ winners were selected"; + +"BoostGift.StartConfirmation.Title" = "Start Giveaway"; +"BoostGift.StartConfirmation.Text" = "Are you sure you want to start giveaway now?"; +"BoostGift.StartConfirmation.Start" = "Start"; + +"Channel.Info.Stats" = "Statistics and Boosts"; diff --git a/Telegram/Telegram-iOS/en.lproj/NiceLocalizable.strings b/Telegram/Telegram-iOS/en.lproj/NiceLocalizable.strings index a0a18c0d88c..34a559543cf 100644 --- a/Telegram/Telegram-iOS/en.lproj/NiceLocalizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/NiceLocalizable.strings @@ -182,12 +182,6 @@ /*Telegram Premium*/ "TelegramPremium.Description" = "Unfortunately, you can’t get **Telegram Premium Features** while being logged in to the Nicegram app. Please go to the official Telegram app and subscribe to **Telegram Premium**. As you go back to Nicegram, all **Premium Features** will be available to use."; -"TelegramPremium.Title" = "Restore premium"; -"TelegramPremium.Failure.Title" = "Failure"; -"TelegramPremium.Failure.Description" = "Something went wrong. Please, use Nicegram Bot to restore your Premium Lifetime Subscription"; -"TelegramPremium.Failure.Pending" = "Your request is pending. Please, wait until Nicegram Bot approves it"; -"TelegramPremium.Success.Title" = "Success"; -"TelegramPremium.Success.Description" = "Your Premium Lifetime Subscription has been successfully restored!"; /*Double Bottom*/ "DoubleBottom.Title" = "Double bottom"; diff --git a/bitbucket-pipelines.yml b/bitbucket-pipelines.yml index da1e9382de4..147c32f4bd6 100644 --- a/bitbucket-pipelines.yml +++ b/bitbucket-pipelines.yml @@ -2,7 +2,7 @@ image: atlassian/default-image:3 pipelines: branches: - '{master,beta}': + '{master,beta,beta-*}': - step: script: - BB_REPO_PATH=$BITBUCKET_CLONE_DIR diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index 64888d2baca..77e0c1f5a45 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -1070,7 +1070,7 @@ public protocol AccountContext: AnyObject { public struct PremiumConfiguration { public static var defaultValue: PremiumConfiguration { - return PremiumConfiguration(isPremiumDisabled: false, showPremiumGiftInAttachMenu: false, showPremiumGiftInTextField: false, giveawayGiftsPurchaseAvailable: false, boostsPerGiftCount: 3) + return PremiumConfiguration(isPremiumDisabled: false, showPremiumGiftInAttachMenu: false, showPremiumGiftInTextField: false, giveawayGiftsPurchaseAvailable: false, boostsPerGiftCount: 3, minChannelNameColorLevel: 5) } public let isPremiumDisabled: Bool @@ -1078,13 +1078,15 @@ public struct PremiumConfiguration { public let showPremiumGiftInTextField: Bool public let giveawayGiftsPurchaseAvailable: Bool public let boostsPerGiftCount: Int32 + public let minChannelNameColorLevel: Int32 - fileprivate init(isPremiumDisabled: Bool, showPremiumGiftInAttachMenu: Bool, showPremiumGiftInTextField: Bool, giveawayGiftsPurchaseAvailable: Bool, boostsPerGiftCount: Int32) { + fileprivate init(isPremiumDisabled: Bool, showPremiumGiftInAttachMenu: Bool, showPremiumGiftInTextField: Bool, giveawayGiftsPurchaseAvailable: Bool, boostsPerGiftCount: Int32, minChannelNameColorLevel: Int32) { self.isPremiumDisabled = isPremiumDisabled self.showPremiumGiftInAttachMenu = showPremiumGiftInAttachMenu self.showPremiumGiftInTextField = showPremiumGiftInTextField self.giveawayGiftsPurchaseAvailable = giveawayGiftsPurchaseAvailable self.boostsPerGiftCount = boostsPerGiftCount + self.minChannelNameColorLevel = minChannelNameColorLevel } public static func with(appConfiguration: AppConfiguration) -> PremiumConfiguration { @@ -1094,7 +1096,8 @@ public struct PremiumConfiguration { showPremiumGiftInAttachMenu: data["premium_gift_attach_menu_icon"] as? Bool ?? false, showPremiumGiftInTextField: data["premium_gift_text_field_icon"] as? Bool ?? false, giveawayGiftsPurchaseAvailable: data["giveaway_gifts_purchase_available"] as? Bool ?? false, - boostsPerGiftCount: Int32(data["boosts_per_sent_gift"] as? Double ?? 3) + boostsPerGiftCount: Int32(data["boosts_per_sent_gift"] as? Double ?? 3), + minChannelNameColorLevel: Int32(data["channel_color_level_min"] as? Double ?? 5) ) } else { return .defaultValue diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index ef134e30fdb..36e9575c2dc 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -4495,24 +4495,6 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController private func nicegramInit() { RepoTgHelper.setTelegramId(context.account.peerId.id._internalGetInt64Value()) - Task { - let initAiChatUseCase = AiChatTgHelper.resolveInitAiChatUseCase() - try await initAiChatUseCase() - } - - if #available(iOS 15.0, *) { - Task { - let checkAiPremiumSubUseCase = AiChatTgHelper.resolveCheckAiPremiumSubUseCase() - await checkAiPremiumSubUseCase() - - let getCurrentUserUseCase = RepoUserTgHelper.resolveGetCurrentUserUseCase() - if getCurrentUserUseCase.isAuthorized() { - let refreshAiBillingInfoUseCase = AiChatTgHelper.resolveRefreshAiBillingInfoUseCase() - try await refreshAiBillingInfoUseCase() - } - } - } - // Localization let _ = context.sharedContext.accountManager.sharedData(keys: [SharedDataKeys.localizationSettings]).start { sharedData in if let localizationSettings = sharedData.entries[SharedDataKeys.localizationSettings]?.get(LocalizationSettings.self) { diff --git a/submodules/ChatListUI/Sources/Node/ChatListItem.swift b/submodules/ChatListUI/Sources/Node/ChatListItem.swift index 0e83174898b..79403326dbd 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift @@ -2553,6 +2553,15 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { } } } + + // MARK: Nicegram PinnedChats + if let nicegramBadge = item.nicegramItem?.badge { + currentCredibilityIconContent = .image( + image: nicegramBadge + ) + } + // + if let currentSecretIconImage = currentSecretIconImage { titleIconsWidth += currentSecretIconImage.size.width + 2.0 } diff --git a/submodules/ChatListUI/Sources/Node/ChatListItemStrings.swift b/submodules/ChatListUI/Sources/Node/ChatListItemStrings.swift index 060ab6bd018..4778982f7b3 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItemStrings.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItemStrings.swift @@ -306,8 +306,12 @@ public func chatListItemStrings(strings: PresentationStrings, nameDisplayOrder: messageText = strings.Notification_Story } case _ as TelegramMediaGiveaway: - messageText = strings.Message_Giveaway - case let webpage as TelegramMediaWebpage: + if let forwardInfo = message.forwardInfo, let author = forwardInfo.author { + messageText = strings.Message_GiveawayStartedOther(EnginePeer(author).compactDisplayTitle).string + } else { + messageText = strings.Message_GiveawayStarted + } + case let webpage as TelegramMediaWebpage: if messageText.isEmpty, case let .Loaded(content) = webpage.content { messageText = content.displayUrl } diff --git a/submodules/DatePickerNode/Sources/DatePickerNode.swift b/submodules/DatePickerNode/Sources/DatePickerNode.swift index 06fb60598ff..61ca6ce416d 100644 --- a/submodules/DatePickerNode/Sources/DatePickerNode.swift +++ b/submodules/DatePickerNode/Sources/DatePickerNode.swift @@ -666,6 +666,13 @@ public final class DatePickerNode: ASDisplayNode { } } + if let date = calendar.date(from: dateComponents), date > self.maximumDate { + let maximumDateComponents = calendar.dateComponents([.hour, .minute, .day, .month, .year], from: self.maximumDate) + if let hour = maximumDateComponents.hour { + dateComponents.hour = hour - 1 + } + } + if let date = calendar.date(from: dateComponents), date >= self.minimumDate && date < self.maximumDate { let updatedState = State(minDate: self.state.minDate, maxDate: self.state.maxDate, date: date, displayingMonthSelection: self.state.displayingMonthSelection, displayingDateSelection: self.state.displayingDateSelection, displayingTimeSelection: self.state.displayingTimeSelection, selectedMonth: monthNode.month) self.updateState(updatedState, animated: false) diff --git a/submodules/Display/Source/TextNode.swift b/submodules/Display/Source/TextNode.swift index c5e17b65e9b..6326867e41b 100644 --- a/submodules/Display/Source/TextNode.swift +++ b/submodules/Display/Source/TextNode.swift @@ -10,6 +10,10 @@ private let quoteIcon: UIImage = { return UIImage(bundleImageName: "Chat/Message/ReplyQuoteIcon")!.precomposed() }() +private let codeIcon: UIImage = { + return UIImage(bundleImageName: "Chat/Message/TextCodeIcon")!.precomposed() +}() + private final class TextNodeStrikethrough { let range: NSRange let frame: CGRect @@ -1363,7 +1367,9 @@ open class TextNode: ASDisplayNode { case .quote: additionalSegmentRightInset = blockQuoteIconInset case .code: - break + if segment.title != nil { + additionalSegmentRightInset = blockQuoteIconInset + } } } @@ -2245,7 +2251,17 @@ open class TextNode: ASDisplayNode { context.restoreGState() context.resetClip() case .code: - break + if blockQuote.data.title != nil { + let quoteRect = CGRect(origin: CGPoint(x: blockFrame.maxX - 4.0 - codeIcon.size.width, y: blockFrame.minY + 4.0), size: codeIcon.size) + context.saveGState() + context.translateBy(x: quoteRect.midX, y: quoteRect.midY) + context.scaleBy(x: 1.0, y: -1.0) + context.translateBy(x: -quoteRect.midX, y: -quoteRect.midY) + context.clip(to: quoteRect, mask: codeIcon.cgImage!) + context.fill(quoteRect) + context.restoreGState() + context.resetClip() + } } let lineFrame = CGRect(origin: CGPoint(x: blockFrame.minX, y: blockFrame.minY), size: CGSize(width: lineWidth, height: blockFrame.height)) diff --git a/submodules/LegacyComponents/Sources/TGMediaVideoConverter.m b/submodules/LegacyComponents/Sources/TGMediaVideoConverter.m index 86190fd87f6..15189ec41f5 100644 --- a/submodules/LegacyComponents/Sources/TGMediaVideoConverter.m +++ b/submodules/LegacyComponents/Sources/TGMediaVideoConverter.m @@ -241,7 +241,7 @@ + (SSignal *)renderUIImage:(UIImage *)image duration:(NSTimeInterval)duration ad SAtomic *context = [[SAtomic alloc] initWithValue:[TGMediaVideoConversionContext contextWithQueue:queue subscriber:subscriber]]; NSURL *outputUrl = [NSURL fileURLWithPath:path]; - NSString *path = TGComponentsPathForResource(@"blank", @"mp4"); + NSString *path = TGComponentsPathForResource(@"BlankVideo", @"m4v"); AVAsset *avAsset = [[AVURLAsset alloc] initWithURL:[NSURL fileURLWithPath:path] options:nil]; NSArray *requiredKeys = @[ @"tracks", @"duration", @"playable" ]; diff --git a/submodules/Postbox/Sources/PeerMergedOperationLogIndexTable.swift b/submodules/Postbox/Sources/PeerMergedOperationLogIndexTable.swift index b04a4652c3d..b2572299d65 100644 --- a/submodules/Postbox/Sources/PeerMergedOperationLogIndexTable.swift +++ b/submodules/Postbox/Sources/PeerMergedOperationLogIndexTable.swift @@ -52,6 +52,30 @@ final class PeerMergedOperationLogIndexTable: Table { return result } + func getTagLocalIndices(tag: PeerOperationLogTag, peerId: PeerId, fromMergedIndex: Int32, limit: Int) -> [(PeerId, Int32, Int32)] { + var result: [(PeerId, Int32, Int32)] = [] + self.valueBox.range(self.table, start: self.key(tag: tag, index: fromMergedIndex == 0 ? 0 : fromMergedIndex - 1), end: self.key(tag: tag, index: Int32.max), values: { key, value in + assert(key.getUInt8(0) == tag.rawValue) + var peerIdValue: Int64 = 0 + var tagLocalIndexValue: Int32 = 0 + value.read(&peerIdValue, offset: 0, length: 8) + value.read(&tagLocalIndexValue, offset: 0, length: 4) + + let parsedPeerId = PeerId(peerIdValue) + if parsedPeerId != peerId { + return true + } + + result.append((parsedPeerId, tagLocalIndexValue, key.getInt32(1))) + if result.count >= limit { + return false + } + + return true + }, limit: 0) + return result + } + func tailIndex(tag: PeerOperationLogTag) -> Int32? { var result: Int32? self.valueBox.range(self.table, start: self.key(tag: tag, index: Int32.max), end: self.key(tag: tag, index: 0), keys: { diff --git a/submodules/Postbox/Sources/PeerMergedOperationLogView.swift b/submodules/Postbox/Sources/PeerMergedOperationLogView.swift index af2daac0f62..8912d2c830a 100644 --- a/submodules/Postbox/Sources/PeerMergedOperationLogView.swift +++ b/submodules/Postbox/Sources/PeerMergedOperationLogView.swift @@ -2,23 +2,52 @@ import Foundation final class MutablePeerMergedOperationLogView { let tag: PeerOperationLogTag + let filterByPeerId: PeerId? var entries: [PeerMergedOperationLogEntry] var tailIndex: Int32? let limit: Int - init(postbox: PostboxImpl, tag: PeerOperationLogTag, limit: Int) { + init(postbox: PostboxImpl, tag: PeerOperationLogTag, filterByPeerId: PeerId?, limit: Int) { self.tag = tag - self.entries = postbox.peerOperationLogTable.getMergedEntries(tag: tag, fromIndex: 0, limit: limit) + self.filterByPeerId = filterByPeerId + if let filterByPeerId = self.filterByPeerId { + self.entries = postbox.peerOperationLogTable.getMergedEntries(tag: tag, peerId: filterByPeerId, fromIndex: 0, limit: limit) + } else { + self.entries = postbox.peerOperationLogTable.getMergedEntries(tag: tag, fromIndex: 0, limit: limit) + } self.tailIndex = postbox.peerMergedOperationLogIndexTable.tailIndex(tag: tag) self.limit = limit } func replay(postbox: PostboxImpl, operations: [PeerMergedOperationLogOperation]) -> Bool { var updated = false - var invalidatedTail = false - for operation in operations { - switch operation { + if let filterByPeerId = self.filterByPeerId { + if operations.contains(where: { operation in + switch operation { + case let .append(entry): + if entry.tag == self.tag && entry.peerId == filterByPeerId { + return true + } + case let .remove(tag, peerId, _): + if tag == self.tag && peerId == filterByPeerId { + return true + } + case let .updateContents(entry): + if entry.tag == self.tag && entry.peerId == filterByPeerId { + return true + } + } + return false + }) { + self.entries = postbox.peerOperationLogTable.getMergedEntries(tag: tag, peerId: filterByPeerId, fromIndex: 0, limit: limit) + updated = true + } + } else { + var invalidatedTail = false + + for operation in operations { + switch operation { case let .append(entry): if entry.tag == self.tag { if let tailIndex = self.tailIndex { @@ -39,15 +68,15 @@ final class MutablePeerMergedOperationLogView { } case let .updateContents(entry): if entry.tag == self.tag { - loop: for i in 0 ..< self.entries.count { - if self.entries[i].tagLocalIndex == entry.tagLocalIndex { - self.entries[i] = entry - updated = true - break loop - } + loop: for i in 0 ..< self.entries.count { + if self.entries[i].tagLocalIndex == entry.tagLocalIndex { + self.entries[i] = entry + updated = true + break loop } } - case let .remove(tag, mergedIndices): + } + case let .remove(tag, _, mergedIndices): if tag == self.tag { updated = true for i in (0 ..< self.entries.count).reversed() { @@ -60,37 +89,38 @@ final class MutablePeerMergedOperationLogView { invalidatedTail = true } } + } } - } - - if updated { - if invalidatedTail { - self.tailIndex = postbox.peerMergedOperationLogIndexTable.tailIndex(tag: self.tag) - } - if self.entries.count < self.limit { - if let tailIndex = self.tailIndex { - if self.entries.isEmpty || self.entries.last!.mergedIndex < tailIndex { - var fromIndex: Int32 = 0 - if !self.entries.isEmpty { - fromIndex = self.entries.last!.mergedIndex + 1 - } - for entry in postbox.peerOperationLogTable.getMergedEntries(tag: self.tag, fromIndex: fromIndex, limit: self.limit - self.entries.count) { - self.entries.append(entry) - } - for i in 0 ..< self.entries.count { - if i != 0 { - assert(self.entries[i].mergedIndex >= self.entries[i - 1].mergedIndex + 1) + + if updated { + if invalidatedTail { + self.tailIndex = postbox.peerMergedOperationLogIndexTable.tailIndex(tag: self.tag) + } + if self.entries.count < self.limit { + if let tailIndex = self.tailIndex { + if self.entries.isEmpty || self.entries.last!.mergedIndex < tailIndex { + var fromIndex: Int32 = 0 + if !self.entries.isEmpty { + fromIndex = self.entries.last!.mergedIndex + 1 + } + for entry in postbox.peerOperationLogTable.getMergedEntries(tag: self.tag, fromIndex: fromIndex, limit: self.limit - self.entries.count) { + self.entries.append(entry) + } + for i in 0 ..< self.entries.count { + if i != 0 { + assert(self.entries[i].mergedIndex >= self.entries[i - 1].mergedIndex + 1) + } + } + if !self.entries.isEmpty { + assert(self.entries.last!.mergedIndex <= tailIndex) } - } - if !self.entries.isEmpty { - assert(self.entries.last!.mergedIndex <= tailIndex) } } - } - } else { - assert(self.tailIndex != nil) - if let tailIndex = self.tailIndex { - assert(self.entries.last!.mergedIndex <= tailIndex) + } else { + assert(self.tailIndex != nil) + if let tailIndex = self.tailIndex { + assert(self.entries.last!.mergedIndex <= tailIndex) + } } } } diff --git a/submodules/Postbox/Sources/PeerOperationLogTable.swift b/submodules/Postbox/Sources/PeerOperationLogTable.swift index f3c3a9cc959..34e828b4c93 100644 --- a/submodules/Postbox/Sources/PeerOperationLogTable.swift +++ b/submodules/Postbox/Sources/PeerOperationLogTable.swift @@ -2,7 +2,7 @@ import Foundation enum PeerMergedOperationLogOperation { case append(PeerMergedOperationLogEntry) - case remove(tag: PeerOperationLogTag, mergedIndices: Set) + case remove(tag: PeerOperationLogTag, peerId: PeerId, mergedIndices: Set) case updateContents(PeerMergedOperationLogEntry) } @@ -197,7 +197,7 @@ final class PeerOperationLogTable: Table { if !mergedIndices.isEmpty { self.mergedIndexTable.remove(tag: tag, mergedIndices: mergedIndices) - operations.append(.remove(tag: tag, mergedIndices: Set(mergedIndices))) + operations.append(.remove(tag: tag, peerId: peerId, mergedIndices: Set(mergedIndices))) } return removed } @@ -224,7 +224,7 @@ final class PeerOperationLogTable: Table { if !mergedIndices.isEmpty { self.mergedIndexTable.remove(tag: tag, mergedIndices: mergedIndices) - operations.append(.remove(tag: tag, mergedIndices: Set(mergedIndices))) + operations.append(.remove(tag: tag, peerId: peerId, mergedIndices: Set(mergedIndices))) } } @@ -250,7 +250,7 @@ final class PeerOperationLogTable: Table { if !mergedIndices.isEmpty { self.mergedIndexTable.remove(tag: tag, mergedIndices: mergedIndices) - operations.append(.remove(tag: tag, mergedIndices: Set(mergedIndices))) + operations.append(.remove(tag: tag, peerId: peerId, mergedIndices: Set(mergedIndices))) } } @@ -271,6 +271,23 @@ final class PeerOperationLogTable: Table { return entries } + func getMergedEntries(tag: PeerOperationLogTag, peerId: PeerId, fromIndex: Int32, limit: Int) -> [PeerMergedOperationLogEntry] { + var entries: [PeerMergedOperationLogEntry] = [] + for (peerId, tagLocalIndex, mergedIndex) in self.mergedIndexTable.getTagLocalIndices(tag: tag, peerId: peerId, fromMergedIndex: fromIndex, limit: limit) { + if let value = self.valueBox.get(self.table, key: self.key(peerId: peerId, tag: tag, index: tagLocalIndex)) { + if let entry = parseMergedEntry(peerId: peerId, tag: tag, tagLocalIndex: tagLocalIndex, value) { + entries.append(entry) + } else { + assertionFailure() + } + } else { + self.mergedIndexTable.remove(tag: tag, mergedIndices: [mergedIndex]) + assertionFailure() + } + } + return entries + } + func enumerateEntries(peerId: PeerId, tag: PeerOperationLogTag, _ f: (PeerOperationLogEntry) -> Bool) { self.valueBox.range(self.table, start: self.key(peerId: peerId, tag: tag, index: 0).predecessor, end: self.key(peerId: peerId, tag: tag, index: Int32.max).successor, values: { key, value in if let entry = parseEntry(peerId: peerId, tag: tag, tagLocalIndex: key.getInt32(9), value) { @@ -317,12 +334,12 @@ final class PeerOperationLogTable: Table { if let mergedIndexValue = mergedIndex { mergedIndex = nil self.mergedIndexTable.remove(tag: tag, mergedIndices: [mergedIndexValue]) - operations.append(.remove(tag: tag, mergedIndices: Set([mergedIndexValue]))) + operations.append(.remove(tag: tag, peerId: peerId, mergedIndices: Set([mergedIndexValue]))) } case .newAutomatic: if let mergedIndexValue = mergedIndex { self.mergedIndexTable.remove(tag: tag, mergedIndices: [mergedIndexValue]) - operations.append(.remove(tag: tag, mergedIndices: Set([mergedIndexValue]))) + operations.append(.remove(tag: tag, peerId: peerId, mergedIndices: Set([mergedIndexValue]))) } let updatedMergedIndexValue = self.mergedIndexTable.add(peerId: peerId, tag: tag, tagLocalIndex: tagLocalIndex) mergedIndex = updatedMergedIndexValue diff --git a/submodules/Postbox/Sources/Postbox.swift b/submodules/Postbox/Sources/Postbox.swift index 66ba36c8716..5787fc1ea06 100644 --- a/submodules/Postbox/Sources/Postbox.swift +++ b/submodules/Postbox/Sources/Postbox.swift @@ -3679,9 +3679,9 @@ final class PostboxImpl { } } - public func mergedOperationLogView(tag: PeerOperationLogTag, limit: Int) -> Signal { + public func mergedOperationLogView(tag: PeerOperationLogTag, filterByPeerId: PeerId?, limit: Int) -> Signal { return self.transactionSignal { subscriber, transaction in - let view = MutablePeerMergedOperationLogView(postbox: self, tag: tag, limit: limit) + let view = MutablePeerMergedOperationLogView(postbox: self, tag: tag, filterByPeerId: filterByPeerId, limit: limit) subscriber.putNext(PeerMergedOperationLogView(view)) @@ -4663,12 +4663,12 @@ public class Postbox { } } - public func mergedOperationLogView(tag: PeerOperationLogTag, limit: Int) -> Signal { + public func mergedOperationLogView(tag: PeerOperationLogTag, filterByPeerId: PeerId? = nil, limit: Int) -> Signal { return Signal { subscriber in let disposable = MetaDisposable() self.impl.with { impl in - disposable.set(impl.mergedOperationLogView(tag: tag, limit: limit).start(next: subscriber.putNext, error: subscriber.putError, completed: subscriber.putCompletion)) + disposable.set(impl.mergedOperationLogView(tag: tag, filterByPeerId: filterByPeerId, limit: limit).start(next: subscriber.putNext, error: subscriber.putError, completed: subscriber.putCompletion)) } return disposable diff --git a/submodules/PremiumUI/Sources/CreateGiveawayController.swift b/submodules/PremiumUI/Sources/CreateGiveawayController.swift index 6b9b7dbb023..4897e22b52a 100644 --- a/submodules/PremiumUI/Sources/CreateGiveawayController.swift +++ b/submodules/PremiumUI/Sources/CreateGiveawayController.swift @@ -837,7 +837,14 @@ public func createGiveawayController(context: AccountContext, updatedPresentatio badgeCount = Int32(state.peers.count) * 4 } let footerItem = CreateGiveawayFooterItem(theme: presentationData.theme, title: state.mode == .gift ? presentationData.strings.BoostGift_GiftPremium : presentationData.strings.BoostGift_StartGiveaway, badgeCount: badgeCount, isLoading: state.updating, action: { - buyActionImpl?() + if case .prepaid = subject { + let alertController = textAlertController(context: context, title: presentationData.strings.BoostGift_StartConfirmation_Title, text: presentationData.strings.BoostGift_StartConfirmation_Text, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.BoostGift_StartConfirmation_Start, action: { + buyActionImpl?() + })], parseMarkdown: true) + presentControllerImpl?(alertController) + } else { + buyActionImpl?() + } }) let leftNavigationButton = ItemListNavigationButton(content: .none, style: .regular, enabled: false, action: {}) @@ -896,58 +903,58 @@ public func createGiveawayController(context: AccountContext, updatedPresentatio buyActionImpl = { [weak controller] in let state = stateValue.with { $0 } - guard let products = productsValue.with({ $0 }), !products.isEmpty else { - return - } - + let presentationData = context.sharedContext.currentPresentationData.with { $0 } - - var selectedProduct: PremiumGiftProduct? - let selectedMonths = state.selectedMonths ?? 12 - switch state.mode { - case .giveaway: - if let product = products.first(where: { $0.months == selectedMonths && $0.giftOption.users == state.subscriptions }) { - selectedProduct = product - } - case .gift: - if let product = products.first(where: { $0.months == selectedMonths && $0.giftOption.users == 1 }) { - selectedProduct = product - } - } - - guard let selectedProduct else { - let alertController = textAlertController(context: context, title: presentationData.strings.BoostGift_ReduceQuantity_Title, text: presentationData.strings.BoostGift_ReduceQuantity_Text("\(state.subscriptions)", "\(selectedMonths)", "\(25)").string, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.BoostGift_ReduceQuantity_Reduce, action: { - updateState { state in - var updatedState = state - updatedState.subscriptions = 25 - return updatedState - } - })], parseMarkdown: true) - presentControllerImpl?(alertController) - return - } - - let (currency, amount) = selectedProduct.storeProduct.priceCurrencyAndAmount - - let purpose: AppStoreTransactionPurpose - let quantity: Int32 - switch state.mode { - case .giveaway: - purpose = .giveaway(boostPeer: peerId, additionalPeerIds: state.channels.filter { $0 != peerId }, countries: state.countries, onlyNewSubscribers: state.onlyNewEligible, randomId: Int64.random(in: .min ..< .max), untilDate: state.time, currency: currency, amount: amount) - quantity = selectedProduct.giftOption.storeQuantity - case .gift: - purpose = .giftCode(peerIds: state.peers, boostPeer: peerId, currency: currency, amount: amount) - quantity = Int32(state.peers.count) - } - - updateState { state in - var updatedState = state - updatedState.updating = true - return updatedState - } - + switch subject { case .generic: + guard let products = productsValue.with({ $0 }), !products.isEmpty else { + return + } + var selectedProduct: PremiumGiftProduct? + let selectedMonths = state.selectedMonths ?? 12 + switch state.mode { + case .giveaway: + if let product = products.first(where: { $0.months == selectedMonths && $0.giftOption.users == state.subscriptions }) { + selectedProduct = product + } + case .gift: + if let product = products.first(where: { $0.months == selectedMonths && $0.giftOption.users == 1 }) { + selectedProduct = product + } + } + + guard let selectedProduct else { + let alertController = textAlertController(context: context, title: presentationData.strings.BoostGift_ReduceQuantity_Title, text: presentationData.strings.BoostGift_ReduceQuantity_Text("\(state.subscriptions)", "\(selectedMonths)", "\(25)").string, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.BoostGift_ReduceQuantity_Reduce, action: { + updateState { state in + var updatedState = state + updatedState.subscriptions = 25 + return updatedState + } + })], parseMarkdown: true) + presentControllerImpl?(alertController) + return + } + + updateState { state in + var updatedState = state + updatedState.updating = true + return updatedState + } + + let (currency, amount) = selectedProduct.storeProduct.priceCurrencyAndAmount + + let purpose: AppStoreTransactionPurpose + let quantity: Int32 + switch state.mode { + case .giveaway: + purpose = .giveaway(boostPeer: peerId, additionalPeerIds: state.channels.filter { $0 != peerId }, countries: state.countries, onlyNewSubscribers: state.onlyNewEligible, randomId: Int64.random(in: .min ..< .max), untilDate: state.time, currency: currency, amount: amount) + quantity = selectedProduct.giftOption.storeQuantity + case .gift: + purpose = .giftCode(peerIds: state.peers, boostPeer: peerId, currency: currency, amount: amount) + quantity = Int32(state.peers.count) + } + let _ = (context.engine.payments.canPurchasePremium(purpose: purpose) |> deliverOnMainQueue).startStandalone(next: { [weak controller] available in if available, let inAppPurchaseManager = context.inAppPurchaseManager { @@ -1033,6 +1040,12 @@ public func createGiveawayController(context: AccountContext, updatedPresentatio } }) case let .prepaid(prepaidGiveaway): + updateState { state in + var updatedState = state + updatedState.updating = true + return updatedState + } + let _ = (context.engine.payments.launchPrepaidGiveaway(peerId: peerId, id: prepaidGiveaway.id, additionalPeerIds: state.channels.filter { $0 != peerId }, countries: state.countries, onlyNewSubscribers: state.onlyNewEligible, randomId: Int64.random(in: .min ..< .max), untilDate: state.time) |> deliverOnMainQueue).startStandalone(completed: { if let controller, let navigationController = controller.navigationController as? NavigationController { diff --git a/submodules/PremiumUI/Sources/GiftOptionItem.swift b/submodules/PremiumUI/Sources/GiftOptionItem.swift index 239293955af..b7fe236e663 100644 --- a/submodules/PremiumUI/Sources/GiftOptionItem.swift +++ b/submodules/PremiumUI/Sources/GiftOptionItem.swift @@ -304,12 +304,14 @@ class GiftOptionItemNode: ItemListRevealOptionsItemNode { let (labelLayout, labelApply) = makeLabelLayout(TextNodeLayoutArguments(attributedString: labelAttributedString, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width, height: .greatestFiniteMagnitude))) var textConstrainedWidth = params.width - leftInset - 8.0 - editingOffset - rightInset - labelLayout.size.width - avatarInset + var subtitleConstrainedWidth = textConstrainedWidth if let label = item.label, case .semitransparent = label { textConstrainedWidth -= 54.0 + subtitleConstrainedWidth -= 30.0 } let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleAttributedString, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: textConstrainedWidth, height: .greatestFiniteMagnitude))) - let (statusLayout, statusApply) = makeStatusLayout(TextNodeLayoutArguments(attributedString: statusAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: textConstrainedWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + let (statusLayout, statusApply) = makeStatusLayout(TextNodeLayoutArguments(attributedString: statusAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: subtitleConstrainedWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) let (badgeLayout, badgeApply) = makeBadgeLayout(TextNodeLayoutArguments(attributedString: badgeAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: textConstrainedWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) diff --git a/submodules/PremiumUI/Sources/GiveawayInfoController.swift b/submodules/PremiumUI/Sources/GiveawayInfoController.swift index 981c4a7adaf..390a02f78b4 100644 --- a/submodules/PremiumUI/Sources/GiveawayInfoController.swift +++ b/submodules/PremiumUI/Sources/GiveawayInfoController.swift @@ -57,7 +57,10 @@ public func presentGiveawayInfoController( switch giveawayInfo { case let .ongoing(start, status): - let startDate = stringForDate(timestamp: start, timeZone: timeZone, strings: presentationData.strings) + let startDate = presentationData.strings.Chat_Giveaway_Info_FullDate( + stringForMessageTimestamp(timestamp: start, dateTimeFormat: presentationData.dateTimeFormat), + stringForDate(timestamp: start, timeZone: timeZone, strings: presentationData.strings) + ).string.trimmingCharacters(in: CharacterSet(charactersIn: "*")) title = presentationData.strings.Chat_Giveaway_Info_Title @@ -123,7 +126,11 @@ public func presentGiveawayInfoController( text = "\(intro)\n\n\(ending)\(participation)" case let .finished(status, start, finish, _, activatedCount): - let startDate = stringForDate(timestamp: start, timeZone: timeZone, strings: presentationData.strings) + let startDate = presentationData.strings.Chat_Giveaway_Info_FullDate( + stringForMessageTimestamp(timestamp: start, dateTimeFormat: presentationData.dateTimeFormat), + stringForDate(timestamp: start, timeZone: timeZone, strings: presentationData.strings) + ).string.trimmingCharacters(in: CharacterSet(charactersIn: "*")) + let finishDate = stringForDate(timestamp: finish, timeZone: timeZone, strings: presentationData.strings) title = presentationData.strings.Chat_Giveaway_Info_EndedTitle diff --git a/submodules/PremiumUI/Sources/PremiumLimitScreen.swift b/submodules/PremiumUI/Sources/PremiumLimitScreen.swift index 0ecbf43532c..60b99f9dae2 100644 --- a/submodules/PremiumUI/Sources/PremiumLimitScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumLimitScreen.swift @@ -517,6 +517,10 @@ public class PremiumLimitDisplayComponent: Component { countWidth = 51.0 case 4: countWidth = 60.0 + case 5: + countWidth = 74.0 + case 6: + countWidth = 88.0 default: countWidth = 51.0 } diff --git a/submodules/ShareController/Sources/ShareController.swift b/submodules/ShareController/Sources/ShareController.swift index f89ce097eba..d42564a6d0e 100644 --- a/submodules/ShareController/Sources/ShareController.swift +++ b/submodules/ShareController/Sources/ShareController.swift @@ -999,9 +999,6 @@ public final class ShareController: ViewController { if self.environment.isMainApp { useLegacy = true } - if peerIds.contains(where: { $0.namespace == Namespaces.Peer.SecretChat }) { - useLegacy = true - } if let currentContext = self.currentContext as? ShareControllerAppAccountContext, let data = currentContext.context.currentAppConfiguration.with({ $0 }).data { if let _ = data["ios_disable_modern_sharing"] { useLegacy = true diff --git a/submodules/StatisticsUI/Sources/ChannelStatsController.swift b/submodules/StatisticsUI/Sources/ChannelStatsController.swift index de36e834e42..82e7b01c0e0 100644 --- a/submodules/StatisticsUI/Sources/ChannelStatsController.swift +++ b/submodules/StatisticsUI/Sources/ChannelStatsController.swift @@ -22,7 +22,7 @@ import ShareController import ItemListPeerActionItem import PremiumUI -private let maxUsersDisplayedLimit: Int32 = 5 +private let initialBoostersDisplayedLimit: Int32 = 5 private final class ChannelStatsControllerArguments { let context: AccountContext @@ -652,17 +652,20 @@ public enum ChannelStatsSection { private struct ChannelStatsControllerState: Equatable { let section: ChannelStatsSection let boostersExpanded: Bool + let moreBoostersDisplayed: Int32 let giftsSelected: Bool init() { self.section = .stats self.boostersExpanded = false + self.moreBoostersDisplayed = 0 self.giftsSelected = false } - init(section: ChannelStatsSection, boostersExpanded: Bool, giftsSelected: Bool) { + init(section: ChannelStatsSection, boostersExpanded: Bool, moreBoostersDisplayed: Int32, giftsSelected: Bool) { self.section = section self.boostersExpanded = boostersExpanded + self.moreBoostersDisplayed = moreBoostersDisplayed self.giftsSelected = giftsSelected } @@ -673,6 +676,9 @@ private struct ChannelStatsControllerState: Equatable { if lhs.boostersExpanded != rhs.boostersExpanded { return false } + if lhs.moreBoostersDisplayed != rhs.moreBoostersDisplayed { + return false + } if lhs.giftsSelected != rhs.giftsSelected { return false } @@ -680,15 +686,19 @@ private struct ChannelStatsControllerState: Equatable { } func withUpdatedSection(_ section: ChannelStatsSection) -> ChannelStatsControllerState { - return ChannelStatsControllerState(section: section, boostersExpanded: self.boostersExpanded, giftsSelected: self.giftsSelected) + return ChannelStatsControllerState(section: section, boostersExpanded: self.boostersExpanded, moreBoostersDisplayed: self.moreBoostersDisplayed, giftsSelected: self.giftsSelected) } func withUpdatedBoostersExpanded(_ boostersExpanded: Bool) -> ChannelStatsControllerState { - return ChannelStatsControllerState(section: self.section, boostersExpanded: boostersExpanded, giftsSelected: self.giftsSelected) + return ChannelStatsControllerState(section: self.section, boostersExpanded: boostersExpanded, moreBoostersDisplayed: self.moreBoostersDisplayed, giftsSelected: self.giftsSelected) + } + + func withUpdatedMoreBoostersDisplayed(_ moreBoostersDisplayed: Int32) -> ChannelStatsControllerState { + return ChannelStatsControllerState(section: self.section, boostersExpanded: self.boostersExpanded, moreBoostersDisplayed: moreBoostersDisplayed, giftsSelected: self.giftsSelected) } func withUpdatedGiftsSelected(_ giftsSelected: Bool) -> ChannelStatsControllerState { - return ChannelStatsControllerState(section: self.section, boostersExpanded: self.boostersExpanded, giftsSelected: giftsSelected) + return ChannelStatsControllerState(section: self.section, boostersExpanded: self.boostersExpanded, moreBoostersDisplayed: self.moreBoostersDisplayed, giftsSelected: giftsSelected) } } @@ -826,20 +836,32 @@ private func channelStatsControllerEntries(state: ChannelStatsControllerState, p var boosterIndex: Int32 = 0 var boosters: [ChannelBoostersContext.State.Boost] = selectedState.boosts - var effectiveExpanded = state.boostersExpanded - if boosters.count > maxUsersDisplayedLimit && !state.boostersExpanded { - boosters = Array(boosters.prefix(Int(maxUsersDisplayedLimit))) + + var limit: Int32 + if state.boostersExpanded { + limit = 25 + state.moreBoostersDisplayed } else { - effectiveExpanded = true + limit = initialBoostersDisplayedLimit } + boosters = Array(boosters.prefix(Int(limit))) for booster in boosters { entries.append(.booster(boosterIndex, presentationData.theme, presentationData.dateTimeFormat, booster)) boosterIndex += 1 } - if !effectiveExpanded { - entries.append(.boostersExpand(presentationData.theme, presentationData.strings.Stats_Boosts_ShowMoreBoosts(Int32(selectedState.count) - maxUsersDisplayedLimit))) + let totalBoostsCount = boosters.reduce(Int32(0)) { partialResult, boost in + return partialResult + boost.multiplier + } + + if totalBoostsCount < selectedState.count { + let moreCount: Int32 + if !state.boostersExpanded { + moreCount = min(80, selectedState.count - totalBoostsCount) + } else { + moreCount = min(200, selectedState.count - totalBoostsCount) + } + entries.append(.boostersExpand(presentationData.theme, presentationData.strings.Stats_Boosts_ShowMoreBoosts(moreCount))) } } @@ -862,8 +884,8 @@ private func channelStatsControllerEntries(state: ChannelStatsControllerState, p } public func channelStatsController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, peerId: PeerId, section: ChannelStatsSection = .stats, boostStatus: ChannelBoostStatus? = nil, statsDatacenterId: Int32?) -> ViewController { - let statePromise = ValuePromise(ChannelStatsControllerState(section: section, boostersExpanded: false, giftsSelected: false), ignoreRepeated: true) - let stateValue = Atomic(value: ChannelStatsControllerState(section: section, boostersExpanded: false, giftsSelected: false)) + let statePromise = ValuePromise(ChannelStatsControllerState(section: section, boostersExpanded: false, moreBoostersDisplayed: 0, giftsSelected: false), ignoreRepeated: true) + let stateValue = Atomic(value: ChannelStatsControllerState(section: section, boostersExpanded: false, moreBoostersDisplayed: 0, giftsSelected: false)) let updateState: ((ChannelStatsControllerState) -> ChannelStatsControllerState) -> Void = { f in statePromise.set(stateValue.modify { f($0) }) } @@ -993,7 +1015,20 @@ public func channelStatsController(context: AccountContext, updatedPresentationD pushImpl?(controller) }, expandBoosters: { - updateState { $0.withUpdatedBoostersExpanded(true) } + var giftsSelected = false + updateState { state in + giftsSelected = state.giftsSelected + if state.boostersExpanded { + return state.withUpdatedMoreBoostersDisplayed(state.moreBoostersDisplayed + 50) + } else { + return state.withUpdatedBoostersExpanded(true) + } + } + if giftsSelected { + giftsContext.loadMore() + } else { + boostsContext.loadMore() + } }, openGifts: { let controller = createGiveawayController(context: context, peerId: peerId, subject: .generic) @@ -1075,12 +1110,6 @@ public func channelStatsController(context: AccountContext, updatedPresentationD } }) } - controller.visibleBottomContentOffsetChanged = { offset in - let state = stateValue.with { $0 } - if case let .known(value) = offset, value < 510.0, case .boosts = state.section, state.boostersExpanded { - boostsContext.loadMore() - } - } controller.titleControlValueChanged = { value in updateState { $0.withUpdatedSection(value == 1 ? .boosts : .stats) } } diff --git a/submodules/TelegramCore/Sources/Account/Account.swift b/submodules/TelegramCore/Sources/Account/Account.swift index e2f48357195..fba5dcc0c10 100644 --- a/submodules/TelegramCore/Sources/Account/Account.swift +++ b/submodules/TelegramCore/Sources/Account/Account.swift @@ -1204,7 +1204,7 @@ public class Account { pendingMessageManager?.updatePendingMessageIds(view.ids) })) - self.managedOperationsDisposable.add(managedSecretChatOutgoingOperations(auxiliaryMethods: auxiliaryMethods, postbox: self.postbox, network: self.network).start()) + self.managedOperationsDisposable.add(managedSecretChatOutgoingOperations(auxiliaryMethods: auxiliaryMethods, postbox: self.postbox, network: self.network, accountPeerId: peerId, mode: .all).start()) self.managedOperationsDisposable.add(managedCloudChatRemoveMessagesOperations(postbox: self.postbox, network: self.network, stateManager: self.stateManager).start()) self.managedOperationsDisposable.add(managedAutoremoveMessageOperations(network: self.network, postbox: self.postbox, isRemove: true).start()) self.managedOperationsDisposable.add(managedAutoremoveMessageOperations(network: self.network, postbox: self.postbox, isRemove: false).start()) diff --git a/submodules/TelegramCore/Sources/Network/Network.swift b/submodules/TelegramCore/Sources/Network/Network.swift index 2aa09fccf39..258494f0f3f 100644 --- a/submodules/TelegramCore/Sources/Network/Network.swift +++ b/submodules/TelegramCore/Sources/Network/Network.swift @@ -662,6 +662,9 @@ private final class NetworkHelper: NSObject, MTContextChangeListener { self.contextLoggedOutUpdated = contextLoggedOutUpdated } + deinit { + } + func fetchContextDatacenterPublicKeys(_ context: MTContext, datacenterId: Int) -> MTSignal { return MTSignal { subscriber in let disposable = self.requestPublicKeys(datacenterId).start(next: { next in @@ -737,6 +740,7 @@ public final class Network: NSObject, MTRequestMessageServiceDelegate { private let queue: Queue public let datacenterId: Int public let context: MTContext + private var networkHelper: NetworkHelper? let mtProto: MTProto let requestService: MTRequestMessageService let basePath: String @@ -811,7 +815,7 @@ public final class Network: NSObject, MTRequestMessageServiceDelegate { } let _contextProxyId = self._contextProxyId - context.add(NetworkHelper(requestPublicKeys: { [weak self] id in + let networkHelper = NetworkHelper(requestPublicKeys: { [weak self] id in if let strongSelf = self { return strongSelf.request(Api.functions.help.getCdnConfig()) |> map(Optional.init) @@ -852,7 +856,9 @@ public final class Network: NSObject, MTRequestMessageServiceDelegate { }, contextLoggedOutUpdated: { [weak self] in Logger.shared.log("Network", "contextLoggedOut") self?.loggedOut?() - })) + }) + self.networkHelper = networkHelper + context.add(networkHelper) requestService.delegate = self self._multiplexedRequestManager = MultiplexedRequestManager(takeWorker: { [weak self] target, tag, continueInBackground in diff --git a/submodules/TelegramCore/Sources/PendingMessages/PendingMessageUploadedContent.swift b/submodules/TelegramCore/Sources/PendingMessages/PendingMessageUploadedContent.swift index 0e81fdcf7f9..7025a9bed15 100644 --- a/submodules/TelegramCore/Sources/PendingMessages/PendingMessageUploadedContent.swift +++ b/submodules/TelegramCore/Sources/PendingMessages/PendingMessageUploadedContent.swift @@ -963,5 +963,15 @@ private func uploadedMediaFileContent(network: Network, postbox: Postbox, auxili } } } + |> take(until: { result in + var complete = false + switch result { + case .content: + complete = true + case .progress: + complete = false + } + return SignalTakeAction(passthrough: true, complete: complete) + }) } } diff --git a/submodules/TelegramCore/Sources/PendingMessages/StandaloneSendMessage.swift b/submodules/TelegramCore/Sources/PendingMessages/StandaloneSendMessage.swift index 7e8f5e74404..2d0e9aec51e 100644 --- a/submodules/TelegramCore/Sources/PendingMessages/StandaloneSendMessage.swift +++ b/submodules/TelegramCore/Sources/PendingMessages/StandaloneSendMessage.swift @@ -125,7 +125,12 @@ public func standaloneSendEnqueueMessages( threadId: Int64?, messages: [StandaloneSendEnqueueMessage] ) -> Signal { - let signals: [Signal] = messages.map { message in + struct MessageResult { + var result: PendingMessageUploadedContentResult + var media: [Media] + } + + let signals: [Signal] = messages.map { message in var attributes: [MessageAttribute] = [] var text: String = "" var media: [Media] = [] @@ -184,6 +189,9 @@ public func standaloneSendEnqueueMessages( contentResult = .single(value) } return contentResult + |> map { contentResult in + return MessageResult(result: contentResult, media: media) + } } return combineLatest(signals) @@ -192,21 +200,21 @@ public func standaloneSendEnqueueMessages( } |> mapToSignal { contentResults -> Signal in var progressSum: Float = 0.0 - var allResults: [PendingMessageUploadedContentAndReuploadInfo] = [] + var allResults: [(result: PendingMessageUploadedContentAndReuploadInfo, media: [Media])] = [] var allDone = true - for status in contentResults { - switch status { + for result in contentResults { + switch result.result { case let .progress(value): allDone = false progressSum += value case let .content(content): - allResults.append(content) + allResults.append((content, result.media)) } } if allDone { var sendSignals: [Signal] = [] - for content in allResults { + for (content, media) in allResults { var text: String = "" switch content.content { case let .text(textValue): @@ -218,6 +226,7 @@ public func standaloneSendEnqueueMessages( } sendSignals.append(sendUploadedMessageContent( + auxiliaryMethods: auxiliaryMethods, postbox: postbox, network: network, stateManager: stateManager, @@ -226,6 +235,7 @@ public func standaloneSendEnqueueMessages( content: content, text: text, attributes: [], + media: media, threadId: threadId )) } @@ -241,12 +251,70 @@ public func standaloneSendEnqueueMessages( } } -private func sendUploadedMessageContent(postbox: Postbox, network: Network, stateManager: AccountStateManager, accountPeerId: PeerId, peerId: PeerId, content: PendingMessageUploadedContentAndReuploadInfo, text: String, attributes: [MessageAttribute], threadId: Int64?) -> Signal { +private func sendUploadedMessageContent( + auxiliaryMethods: AccountAuxiliaryMethods, + postbox: Postbox, + network: Network, + stateManager: AccountStateManager, + accountPeerId: PeerId, + peerId: PeerId, + content: PendingMessageUploadedContentAndReuploadInfo, + text: String, + attributes: [MessageAttribute], + media: [Media], + threadId: Int64? +) -> Signal { return postbox.transaction { transaction -> Signal in if peerId.namespace == Namespaces.Peer.SecretChat { - assertionFailure() - //PendingMessageManager.sendSecretMessageContent(transaction: transaction, message: message, content: content) - return .complete() + var secretFile: SecretChatOutgoingFile? + switch content.content { + case let .secretMedia(file, size, key): + if let fileReference = SecretChatOutgoingFileReference(file) { + secretFile = SecretChatOutgoingFile(reference: fileReference, size: size, key: key) + } + default: + break + } + + var layer: SecretChatLayer? + let state = transaction.getPeerChatState(peerId) as? SecretChatState + if let state = state { + switch state.embeddedState { + case .terminated, .handshake: + break + case .basicLayer: + layer = .layer8 + case let .sequenceBasedLayer(sequenceState): + layer = sequenceState.layerNegotiationState.activeLayer.secretChatLayer + } + } + + if let state = state, let layer = layer { + let messageContents = StandaloneSecretMessageContents( + id: Int64.random(in: Int64.min ... Int64.max), + text: text, + attributes: attributes, + media: media.first, + file: secretFile + ) + + let updatedState = addSecretChatOutgoingOperation(transaction: transaction, peerId: peerId, operation: .sendStandaloneMessage(layer: layer, contents: messageContents), state: state) + if updatedState != state { + transaction.setPeerChatState(peerId, state: updatedState) + } + + return managedSecretChatOutgoingOperations( + auxiliaryMethods: auxiliaryMethods, + postbox: postbox, + network: network, + accountPeerId: accountPeerId, + mode: .standaloneComplete(peerId: peerId) + ) + |> castError(StandaloneSendMessagesError.self) + |> ignoreValues + } else { + return .fail(StandaloneSendMessagesError(peerId: peerId, reason: .none)) + } } else if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) { var uniqueId: Int64 = 0 var forwardSourceInfoAttribute: ForwardSourceInfoAttribute? diff --git a/submodules/TelegramCore/Sources/State/ManagedSecretChatOutgoingOperations.swift b/submodules/TelegramCore/Sources/State/ManagedSecretChatOutgoingOperations.swift index d7fe6ea2be7..daf468b03b0 100644 --- a/submodules/TelegramCore/Sources/State/ManagedSecretChatOutgoingOperations.swift +++ b/submodules/TelegramCore/Sources/State/ManagedSecretChatOutgoingOperations.swift @@ -105,11 +105,20 @@ private func takenImmutableOperation(postbox: Postbox, peerId: PeerId, tagLocalI } } -func managedSecretChatOutgoingOperations(auxiliaryMethods: AccountAuxiliaryMethods, postbox: Postbox, network: Network) -> Signal { - return Signal { _ in +enum ManagedSecretChatOutgoingOperationsMode { + case all + case standaloneComplete(peerId: PeerId) +} + +func managedSecretChatOutgoingOperations(auxiliaryMethods: AccountAuxiliaryMethods, postbox: Postbox, network: Network, accountPeerId: PeerId, mode: ManagedSecretChatOutgoingOperationsMode) -> Signal { + return Signal { subscriber in let helper = Atomic(value: ManagedSecretChatOutgoingOperationsHelper()) - let disposable = postbox.mergedOperationLogView(tag: OperationLogTags.SecretOutgoing, limit: 10).start(next: { view in + var filterByPeerId: PeerId? + if case let .standaloneComplete(peerId) = mode { + filterByPeerId = peerId + } + let disposable = postbox.mergedOperationLogView(tag: OperationLogTags.SecretOutgoing, filterByPeerId: filterByPeerId, limit: 10).start(next: { view in let (disposeOperations, beginOperations) = helper.with { helper -> (disposeOperations: [Disposable], beginOperations: [(PeerMergedOperationLogEntry, MetaDisposable)]) in return helper.update(view.entries) } @@ -118,6 +127,10 @@ func managedSecretChatOutgoingOperations(auxiliaryMethods: AccountAuxiliaryMetho disposable.dispose() } + if case .standaloneComplete = mode, view.entries.isEmpty { + subscriber.putCompletion() + } + for (entry, disposable) in beginOperations { let signal = takenImmutableOperation(postbox: postbox, peerId: entry.peerId, tagLocalIndex: entry.tagLocalIndex) |> mapToSignal { entry -> Signal in @@ -128,6 +141,8 @@ func managedSecretChatOutgoingOperations(auxiliaryMethods: AccountAuxiliaryMetho return initialHandshakeAccept(postbox: postbox, network: network, peerId: entry.peerId, accessHash: accessHash, gA: gA, b: b, tagLocalIndex: entry.tagLocalIndex) case let .sendMessage(layer, id, file): return sendMessage(auxiliaryMethods: auxiliaryMethods, postbox: postbox, network: network, messageId: id, file: file, tagLocalIndex: entry.tagLocalIndex, wasDelivered: operation.delivered, layer: layer) + case let .sendStandaloneMessage(layer, contents): + return sendStandaloneMessage(auxiliaryMethods: auxiliaryMethods, postbox: postbox, network: network, accountPeerId: accountPeerId, peerId: entry.peerId, contents: contents, tagLocalIndex: entry.tagLocalIndex, wasDelivered: operation.delivered, layer: layer) case let .reportLayerSupport(layer, actionGloballyUniqueId, layerSupport): return sendServiceActionMessage(postbox: postbox, network: network, peerId: entry.peerId, action: .reportLayerSupport(layer: layer, actionGloballyUniqueId: actionGloballyUniqueId, layerSupport: layerSupport), tagLocalIndex: entry.tagLocalIndex, wasDelivered: operation.delivered) case let .deleteMessages(layer, actionGloballyUniqueId, globallyUniqueIds): @@ -1711,9 +1726,9 @@ private func resourceThumbnailData(auxiliaryMethods: AccountAuxiliaryMethods, me } } -private func messageWithThumbnailData(auxiliaryMethods: AccountAuxiliaryMethods, mediaBox: MediaBox, message: Message) -> Signal<[MediaId: (PixelDimensions, Data)], NoError> { +private func messageWithThumbnailData(auxiliaryMethods: AccountAuxiliaryMethods, mediaBox: MediaBox, media: [Media]) -> Signal<[MediaId: (PixelDimensions, Data)], NoError> { var signals: [Signal<(MediaId, PixelDimensions, Data)?, NoError>] = [] - for media in message.media { + for media in media { if let image = media as? TelegramMediaImage { if let smallestRepresentation = smallestImageRepresentation(image.representations) { signals.append(resourceThumbnailData(auxiliaryMethods: auxiliaryMethods, mediaBox: mediaBox, resource: smallestRepresentation.resource, mediaId: image.imageId)) @@ -1739,7 +1754,7 @@ private func messageWithThumbnailData(auxiliaryMethods: AccountAuxiliaryMethods, private func sendMessage(auxiliaryMethods: AccountAuxiliaryMethods, postbox: Postbox, network: Network, messageId: MessageId, file: SecretChatOutgoingFile?, tagLocalIndex: Int32, wasDelivered: Bool, layer: SecretChatLayer) -> Signal { return postbox.transaction { transaction -> Signal<[MediaId: (PixelDimensions, Data)], NoError> in if let message = transaction.getMessage(messageId) { - return messageWithThumbnailData(auxiliaryMethods: auxiliaryMethods, mediaBox: postbox.mediaBox, message: message) + return messageWithThumbnailData(auxiliaryMethods: auxiliaryMethods, mediaBox: postbox.mediaBox, media: message.media) } else { return .single([:]) } @@ -1840,6 +1855,166 @@ private func sendMessage(auxiliaryMethods: AccountAuxiliaryMethods, postbox: Pos } } +private func sendStandaloneMessage(auxiliaryMethods: AccountAuxiliaryMethods, postbox: Postbox, network: Network, accountPeerId: PeerId, peerId: PeerId, contents: StandaloneSecretMessageContents, tagLocalIndex: Int32, wasDelivered: Bool, layer: SecretChatLayer) -> Signal { + return postbox.transaction { transaction -> Signal<[MediaId: (PixelDimensions, Data)], NoError> in + var media: [Media] = [] + if let value = contents.media { + media.append(value) + } + return messageWithThumbnailData(auxiliaryMethods: auxiliaryMethods, mediaBox: postbox.mediaBox, media: media) + } + |> switchToLatest + |> mapToSignal { thumbnailData -> Signal in + return postbox.transaction { transaction -> Signal in + guard let state = transaction.getPeerChatState(peerId) as? SecretChatState, let peer = transaction.getPeer(peerId) as? TelegramSecretChat else { + return .complete() + } + + let globallyUniqueId = contents.id + + var media: [Media] = [] + if let value = contents.media { + media.append(value) + } + let message = Message( + stableId: 1, + stableVersion: 0, + id: MessageId(peerId: peerId, namespace: Namespaces.Message.Local, id: 1), + globallyUniqueId: globallyUniqueId, + groupingKey: nil, + groupInfo: nil, + threadId: nil, + timestamp: 1, + flags: [], + tags: [], + globalTags: [], + localTags: [], + forwardInfo: nil, + author: nil, + text: contents.text, + attributes: contents.attributes, + media: media, + peers: SimpleDictionary(), + associatedMessages: SimpleDictionary(), + associatedMessageIds: [], + associatedMedia: [:], + associatedThreadInfo: nil, + associatedStories: [:] + ) + + let decryptedMessage = boxedDecryptedMessage(transaction: transaction, message: message, globallyUniqueId: globallyUniqueId, uploadedFile: contents.file, thumbnailData: [:], layer: layer) + return sendBoxedDecryptedMessage(postbox: postbox, network: network, peer: peer, state: state, operationIndex: tagLocalIndex, decryptedMessage: decryptedMessage, globallyUniqueId: globallyUniqueId, file: contents.file, silent: message.muted, asService: wasDelivered, wasDelivered: wasDelivered) + |> mapToSignal { result -> Signal in + return postbox.transaction { transaction -> Void in + let forceRemove: Bool + switch result { + case .message: + forceRemove = false + case .error: + forceRemove = true + } + markOutgoingOperationAsCompleted(transaction: transaction, peerId: peerId, tagLocalIndex: tagLocalIndex, forceRemove: forceRemove) + + var timestamp: Int32? + var encryptedFile: SecretChatFileReference? + if case let .message(result) = result { + switch result { + case let .sentEncryptedMessage(date): + timestamp = date + case let .sentEncryptedFile(date, file): + timestamp = date + encryptedFile = SecretChatFileReference(file) + } + } + + if let timestamp = timestamp { + var updatedMedia: [Media] = [] + for item in media { + if let file = item as? TelegramMediaFile, let encryptedFile = encryptedFile, let sourceFile = contents.file { + let updatedFile = TelegramMediaFile( + fileId: MediaId(namespace: Namespaces.Media.CloudSecretFile, id: encryptedFile.id), + partialReference: nil, + resource: SecretFileMediaResource(fileId: encryptedFile.id, accessHash: encryptedFile.accessHash, containerSize: encryptedFile.size, decryptedSize: sourceFile.size, datacenterId: Int(encryptedFile.datacenterId), key: sourceFile.key), + previewRepresentations: file.previewRepresentations, + videoThumbnails: file.videoThumbnails, + immediateThumbnailData: file.immediateThumbnailData, + mimeType: file.mimeType, + size: file.size, + attributes: file.attributes + ) + updatedMedia.append(updatedFile) + } else if let image = item as? TelegramMediaImage, let encryptedFile = encryptedFile, let sourceFile = contents.file, let representation = image.representations.last { + let updatedImage = TelegramMediaImage( + imageId: MediaId(namespace: Namespaces.Media.CloudSecretImage, id: encryptedFile.id), + representations: [ + TelegramMediaImageRepresentation( + dimensions: representation.dimensions, + resource: SecretFileMediaResource(fileId: encryptedFile.id, accessHash: encryptedFile.accessHash, containerSize: encryptedFile.size, decryptedSize: sourceFile.size, datacenterId: Int(encryptedFile.datacenterId), key: sourceFile.key), + progressiveSizes: [], + immediateThumbnailData: image.immediateThumbnailData, + hasVideo: false, + isPersonal: false + )], + immediateThumbnailData: nil, + reference: nil, + partialReference: nil, + flags: [] + ) + updatedMedia.append(updatedImage) + } else { + updatedMedia.append(item) + } + } + + let entitiesAttribute = message.textEntitiesAttribute + let (tags, globalTags) = tagsForStoreMessage(incoming: false, attributes: contents.attributes, media: updatedMedia, textEntities: entitiesAttribute?.entities, isPinned: false) + + let storedMessage = StoreMessage( + peerId: peerId, + namespace: Namespaces.Message.Local, + globallyUniqueId: globallyUniqueId, + groupingKey: nil, + threadId: nil, + timestamp: timestamp, + flags: [], + tags: tags, + globalTags: globalTags, + localTags: [], + forwardInfo: nil, + authorId: accountPeerId, + text: message.text, + attributes: message.attributes, + media: updatedMedia + ) + + let idMapping = transaction.addMessages([storedMessage], location: .Random) + if let id = idMapping[globallyUniqueId] { + maybeReadSecretOutgoingMessage(transaction: transaction, index: MessageIndex(id: id, timestamp: timestamp)) + } + + var sentStickers: [TelegramMediaFile] = [] + for media in message.media { + if let file = media as? TelegramMediaFile { + if file.isSticker { + sentStickers.append(file) + } + } + } + + for file in sentStickers { + addRecentlyUsedSticker(transaction: transaction, fileReference: .standalone(media: file)) + } + + if case .error(.chatCancelled) = result { + } + } + } + } + } + |> switchToLatest + } +} + private func sendServiceActionMessage(postbox: Postbox, network: Network, peerId: PeerId, action: SecretMessageAction, tagLocalIndex: Int32, wasDelivered: Bool) -> Signal { return postbox.transaction { transaction -> Signal in if let state = transaction.getPeerChatState(peerId) as? SecretChatState, let peer = transaction.getPeer(peerId) as? TelegramSecretChat { diff --git a/submodules/TelegramCore/Sources/State/UserLimitsConfiguration.swift b/submodules/TelegramCore/Sources/State/UserLimitsConfiguration.swift index b5f99b73926..5922c615712 100644 --- a/submodules/TelegramCore/Sources/State/UserLimitsConfiguration.swift +++ b/submodules/TelegramCore/Sources/State/UserLimitsConfiguration.swift @@ -25,7 +25,6 @@ public struct UserLimitsConfiguration: Equatable { public let maxGiveawayChannelsCount: Int32 public let maxGiveawayCountriesCount: Int32 public let maxGiveawayPeriodSeconds: Int32 - public let minChannelNameColorLevel: Int32 public static var defaultValue: UserLimitsConfiguration { return UserLimitsConfiguration( @@ -51,8 +50,7 @@ public struct UserLimitsConfiguration: Equatable { maxStoriesSuggestedReactions: 1, maxGiveawayChannelsCount: 10, maxGiveawayCountriesCount: 10, - maxGiveawayPeriodSeconds: 86400 * 7, - minChannelNameColorLevel: 10 + maxGiveawayPeriodSeconds: 86400 * 7 ) } @@ -79,8 +77,7 @@ public struct UserLimitsConfiguration: Equatable { maxStoriesSuggestedReactions: Int32, maxGiveawayChannelsCount: Int32, maxGiveawayCountriesCount: Int32, - maxGiveawayPeriodSeconds: Int32, - minChannelNameColorLevel: Int32 + maxGiveawayPeriodSeconds: Int32 ) { self.maxPinnedChatCount = maxPinnedChatCount self.maxArchivedPinnedChatCount = maxArchivedPinnedChatCount @@ -105,7 +102,6 @@ public struct UserLimitsConfiguration: Equatable { self.maxGiveawayChannelsCount = maxGiveawayChannelsCount self.maxGiveawayCountriesCount = maxGiveawayCountriesCount self.maxGiveawayPeriodSeconds = maxGiveawayPeriodSeconds - self.minChannelNameColorLevel = minChannelNameColorLevel } } @@ -153,6 +149,5 @@ extension UserLimitsConfiguration { self.maxGiveawayChannelsCount = getGeneralValue("giveaway_add_peers_max", orElse: defaultValue.maxGiveawayChannelsCount) self.maxGiveawayCountriesCount = getGeneralValue("giveaway_countries_max", orElse: defaultValue.maxGiveawayCountriesCount) self.maxGiveawayPeriodSeconds = getGeneralValue("giveaway_period_max", orElse: defaultValue.maxGiveawayPeriodSeconds) - self.minChannelNameColorLevel = getGeneralValue("channel_color_level_min", orElse: defaultValue.minChannelNameColorLevel) } } diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_SecretChatOutgoingOperation.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_SecretChatOutgoingOperation.swift index 54dd67db1ac..59931fec817 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_SecretChatOutgoingOperation.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_SecretChatOutgoingOperation.swift @@ -7,47 +7,97 @@ private enum SecretChatOutgoingFileValue: Int32 { case uploadedLarge = 2 } -public enum SecretChatOutgoingFileReference: PostboxCoding { +public enum SecretChatOutgoingFileReference: PostboxCoding, Codable { case remote(id: Int64, accessHash: Int64) case uploadedRegular(id: Int64, partCount: Int32, md5Digest: String, keyFingerprint: Int32) case uploadedLarge(id: Int64, partCount: Int32, keyFingerprint: Int32) public init(decoder: PostboxDecoder) { switch decoder.decodeInt32ForKey("v", orElse: 0) { - case SecretChatOutgoingFileValue.remote.rawValue: - self = .remote(id: decoder.decodeInt64ForKey("i", orElse: 0), accessHash: decoder.decodeInt64ForKey("a", orElse: 0)) - case SecretChatOutgoingFileValue.uploadedRegular.rawValue: - self = .uploadedRegular(id: decoder.decodeInt64ForKey("i", orElse: 0), partCount: decoder.decodeInt32ForKey("p", orElse: 0), md5Digest: decoder.decodeStringForKey("d", orElse: ""), keyFingerprint: decoder.decodeInt32ForKey("f", orElse: 0)) - case SecretChatOutgoingFileValue.uploadedLarge.rawValue: - self = .uploadedLarge(id: decoder.decodeInt64ForKey("i", orElse: 0), partCount: decoder.decodeInt32ForKey("p", orElse: 0), keyFingerprint: decoder.decodeInt32ForKey("f", orElse: 0)) - default: - assertionFailure() - self = .remote(id: 0, accessHash: 0) + case SecretChatOutgoingFileValue.remote.rawValue: + self = .remote(id: decoder.decodeInt64ForKey("i", orElse: 0), accessHash: decoder.decodeInt64ForKey("a", orElse: 0)) + case SecretChatOutgoingFileValue.uploadedRegular.rawValue: + self = .uploadedRegular(id: decoder.decodeInt64ForKey("i", orElse: 0), partCount: decoder.decodeInt32ForKey("p", orElse: 0), md5Digest: decoder.decodeStringForKey("d", orElse: ""), keyFingerprint: decoder.decodeInt32ForKey("f", orElse: 0)) + case SecretChatOutgoingFileValue.uploadedLarge.rawValue: + self = .uploadedLarge(id: decoder.decodeInt64ForKey("i", orElse: 0), partCount: decoder.decodeInt32ForKey("p", orElse: 0), keyFingerprint: decoder.decodeInt32ForKey("f", orElse: 0)) + default: + assertionFailure() + self = .remote(id: 0, accessHash: 0) + } + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: StringCodingKey.self) + + switch try container.decode(Int32.self, forKey: "v") { + case SecretChatOutgoingFileValue.remote.rawValue: + self = .remote( + id: try container.decode(Int64.self, forKey: "i"), + accessHash: try container.decode(Int64.self, forKey: "a") + ) + case SecretChatOutgoingFileValue.uploadedRegular.rawValue: + self = .uploadedRegular( + id: try container.decode(Int64.self, forKey: "i"), + partCount: try container.decode(Int32.self, forKey: "p"), + md5Digest: try container.decode(String.self, forKey: "d"), + keyFingerprint: try container.decode(Int32.self, forKey: "f") + ) + case SecretChatOutgoingFileValue.uploadedLarge.rawValue: + self = .uploadedLarge( + id: try container.decode(Int64.self, forKey: "i"), + partCount: try container.decode(Int32.self, forKey: "p"), + keyFingerprint: try container.decode(Int32.self, forKey: "f") + ) + default: + assertionFailure() + self = .remote(id: 0, accessHash: 0) } } public func encode(_ encoder: PostboxEncoder) { switch self { - case let .remote(id, accessHash): - encoder.encodeInt32(SecretChatOutgoingFileValue.remote.rawValue, forKey: "v") - encoder.encodeInt64(id, forKey: "i") - encoder.encodeInt64(accessHash, forKey: "a") - case let .uploadedRegular(id, partCount, md5Digest, keyFingerprint): - encoder.encodeInt32(SecretChatOutgoingFileValue.uploadedRegular.rawValue, forKey: "v") - encoder.encodeInt64(id, forKey: "i") - encoder.encodeInt32(partCount, forKey: "p") - encoder.encodeString(md5Digest, forKey: "d") - encoder.encodeInt32(keyFingerprint, forKey: "f") - case let .uploadedLarge(id, partCount, keyFingerprint): - encoder.encodeInt32(SecretChatOutgoingFileValue.uploadedLarge.rawValue, forKey: "v") - encoder.encodeInt64(id, forKey: "i") - encoder.encodeInt32(partCount, forKey: "p") - encoder.encodeInt32(keyFingerprint, forKey: "f") + case let .remote(id, accessHash): + encoder.encodeInt32(SecretChatOutgoingFileValue.remote.rawValue, forKey: "v") + encoder.encodeInt64(id, forKey: "i") + encoder.encodeInt64(accessHash, forKey: "a") + case let .uploadedRegular(id, partCount, md5Digest, keyFingerprint): + encoder.encodeInt32(SecretChatOutgoingFileValue.uploadedRegular.rawValue, forKey: "v") + encoder.encodeInt64(id, forKey: "i") + encoder.encodeInt32(partCount, forKey: "p") + encoder.encodeString(md5Digest, forKey: "d") + encoder.encodeInt32(keyFingerprint, forKey: "f") + case let .uploadedLarge(id, partCount, keyFingerprint): + encoder.encodeInt32(SecretChatOutgoingFileValue.uploadedLarge.rawValue, forKey: "v") + encoder.encodeInt64(id, forKey: "i") + encoder.encodeInt32(partCount, forKey: "p") + encoder.encodeInt32(keyFingerprint, forKey: "f") + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: StringCodingKey.self) + + switch self { + case let .remote(id, accessHash): + try container.encode(SecretChatOutgoingFileValue.remote.rawValue, forKey: "v") + try container.encode(id, forKey: "i") + try container.encode(accessHash, forKey: "a") + case let .uploadedRegular(id, partCount, md5Digest, keyFingerprint): + try container.encode(SecretChatOutgoingFileValue.uploadedRegular.rawValue, forKey: "v") + try container.encode(id, forKey: "i") + try container.encode(partCount, forKey: "p") + try container.encode(md5Digest, forKey: "d") + try container.encode(keyFingerprint, forKey: "f") + case let .uploadedLarge(id, partCount, keyFingerprint): + try container.encode(SecretChatOutgoingFileValue.uploadedLarge.rawValue, forKey: "v") + try container.encode(id, forKey: "i") + try container.encode(partCount, forKey: "p") + try container.encode(keyFingerprint, forKey: "f") } } } -public struct SecretChatOutgoingFile: PostboxCoding { +public struct SecretChatOutgoingFile: PostboxCoding, Codable { public let reference: SecretChatOutgoingFileReference public let size: Int64 public let key: SecretFileEncryptionKey @@ -68,12 +118,32 @@ public struct SecretChatOutgoingFile: PostboxCoding { self.key = SecretFileEncryptionKey(aesKey: decoder.decodeBytesForKey("k")!.makeData(), aesIv: decoder.decodeBytesForKey("i")!.makeData()) } + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: StringCodingKey.self) + + self.reference = try container.decode(SecretChatOutgoingFileReference.self, forKey: "r") + self.size = try container.decode(Int64.self, forKey: "s64") + self.key = SecretFileEncryptionKey( + aesKey: try container.decode(Data.self, forKey: "k"), + aesIv: try container.decode(Data.self, forKey: "i") + ) + } + public func encode(_ encoder: PostboxEncoder) { encoder.encodeObject(self.reference, forKey: "r") encoder.encodeInt64(self.size, forKey: "s64") encoder.encodeBytes(MemoryBuffer(data: self.key.aesKey), forKey: "k") encoder.encodeBytes(MemoryBuffer(data: self.key.aesIv), forKey: "i") } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: StringCodingKey.self) + + try container.encode(self.reference, forKey: "r") + try container.encode(self.size, forKey: "s64") + try container.encode(self.key.aesKey, forKey: "k") + try container.encode(self.key.aesIv, forKey: "i") + } } public enum SecretChatSequenceBasedLayer: Int32 { @@ -112,11 +182,72 @@ private enum SecretChatOutgoingOperationValue: Int32 { case noop = 12 case setMessageAutoremoveTimeout = 13 case terminate = 14 + case sendStandaloneMessage = 15 +} + +public struct StandaloneSecretMessageContents: Codable { + private enum CodingKeys: String, CodingKey { + case id = "i" + case text = "t" + case attributes = "a" + case media = "m" + case file = "f" + } + + public var id: Int64 + public var text: String + public var attributes: [MessageAttribute] + public var media: Media? + public var file: SecretChatOutgoingFile? + + public init(id: Int64, text: String, attributes: [MessageAttribute], media: Media?, file: SecretChatOutgoingFile?) { + self.id = id + self.text = text + self.attributes = attributes + self.media = media + self.file = file + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + self.id = try container.decode(Int64.self, forKey: .id) + self.text = try container.decode(String.self, forKey: .text) + + let attributes = try container.decode([Data].self, forKey: .attributes) + self.attributes = attributes.compactMap { attribute -> MessageAttribute? in + return PostboxDecoder(buffer: MemoryBuffer(data: attribute)).decodeRootObject() as? MessageAttribute + } + self.media = (try container.decodeIfPresent(Data.self, forKey: .media)).flatMap { media in + return PostboxDecoder(buffer: MemoryBuffer(data: media)).decodeRootObject() as? Media + } + self.file = try container.decodeIfPresent(SecretChatOutgoingFile.self, forKey: .file) + } + + 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.text, forKey: .text) + let attributes = self.attributes.map { attribute -> Data in + let innerEncoder = PostboxEncoder() + innerEncoder.encodeRootObject(attribute) + return innerEncoder.makeData() + } + try container.encode(attributes, forKey: .attributes) + try container.encodeIfPresent(self.media.flatMap { media in + let innerEncoder = PostboxEncoder() + innerEncoder.encodeRootObject(media) + return innerEncoder.makeData() + }, forKey: .media) + try container.encodeIfPresent(self.file, forKey: .file) + } } public enum SecretChatOutgoingOperationContents: PostboxCoding { case initialHandshakeAccept(gA: MemoryBuffer, accessHash: Int64, b: MemoryBuffer) case sendMessage(layer: SecretChatLayer, id: MessageId, file: SecretChatOutgoingFile?) + case sendStandaloneMessage(layer: SecretChatLayer, contents: StandaloneSecretMessageContents) case readMessagesContent(layer: SecretChatLayer, actionGloballyUniqueId: Int64, globallyUniqueIds: [Int64]) case deleteMessages(layer: SecretChatLayer, actionGloballyUniqueId: Int64, globallyUniqueIds: [Int64]) case screenshotMessages(layer: SecretChatLayer, actionGloballyUniqueId: Int64, globallyUniqueIds: [Int64], messageId: MessageId) @@ -137,6 +268,11 @@ public enum SecretChatOutgoingOperationContents: PostboxCoding { self = .initialHandshakeAccept(gA: decoder.decodeBytesForKey("g")!, accessHash: decoder.decodeInt64ForKey("h", orElse: 0), b: decoder.decodeBytesForKey("b")!) case SecretChatOutgoingOperationValue.sendMessage.rawValue: self = .sendMessage(layer: SecretChatLayer(rawValue: decoder.decodeInt32ForKey("l", orElse: 0))!, id: MessageId(peerId: PeerId(decoder.decodeInt64ForKey("i.p", orElse: 0)), namespace: decoder.decodeInt32ForKey("i.n", orElse: 0), id: decoder.decodeInt32ForKey("i.i", orElse: 0)), file: decoder.decodeObjectForKey("f", decoder: { SecretChatOutgoingFile(decoder: $0) }) as? SecretChatOutgoingFile) + case SecretChatOutgoingOperationValue.sendStandaloneMessage.rawValue: + self = .sendStandaloneMessage( + layer: SecretChatLayer(rawValue: decoder.decodeInt32ForKey("l", orElse: 0))!, + contents: decoder.decodeCodable(StandaloneSecretMessageContents.self, forKey: "c") ?? StandaloneSecretMessageContents(id: 0, text: "", attributes: [], media: nil, file: nil) + ) case SecretChatOutgoingOperationValue.readMessagesContent.rawValue: self = .readMessagesContent(layer: SecretChatLayer(rawValue: decoder.decodeInt32ForKey("l", orElse: 0))!, actionGloballyUniqueId: decoder.decodeInt64ForKey("i", orElse: 0), globallyUniqueIds: decoder.decodeInt64ArrayForKey("u")) case SecretChatOutgoingOperationValue.deleteMessages.rawValue: @@ -187,6 +323,10 @@ public enum SecretChatOutgoingOperationContents: PostboxCoding { } else { encoder.encodeNil(forKey: "f") } + case let .sendStandaloneMessage(layer, contents): + encoder.encodeInt32(SecretChatOutgoingOperationValue.sendStandaloneMessage.rawValue, forKey: "r") + encoder.encodeInt32(layer.rawValue, forKey: "l") + encoder.encodeCodable(contents, forKey: "c") case let .readMessagesContent(layer, actionGloballyUniqueId, globallyUniqueIds): encoder.encodeInt32(SecretChatOutgoingOperationValue.readMessagesContent.rawValue, forKey: "r") encoder.encodeInt32(layer.rawValue, forKey: "l") diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Data/ConfigurationData.swift b/submodules/TelegramCore/Sources/TelegramEngine/Data/ConfigurationData.swift index ca16e2f12b1..48a83d7b9a4 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Data/ConfigurationData.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Data/ConfigurationData.swift @@ -59,7 +59,6 @@ public enum EngineConfiguration { public let maxGiveawayChannelsCount: Int32 public let maxGiveawayCountriesCount: Int32 public let maxGiveawayPeriodSeconds: Int32 - public let minChannelNameColorLevel: Int32 public static var defaultValue: UserLimits { return UserLimits(UserLimitsConfiguration.defaultValue) @@ -88,8 +87,7 @@ public enum EngineConfiguration { maxStoriesSuggestedReactions: Int32, maxGiveawayChannelsCount: Int32, maxGiveawayCountriesCount: Int32, - maxGiveawayPeriodSeconds: Int32, - minChannelNameColorLevel: Int32 + maxGiveawayPeriodSeconds: Int32 ) { self.maxPinnedChatCount = maxPinnedChatCount self.maxArchivedPinnedChatCount = maxArchivedPinnedChatCount @@ -114,7 +112,6 @@ public enum EngineConfiguration { self.maxGiveawayChannelsCount = maxGiveawayChannelsCount self.maxGiveawayCountriesCount = maxGiveawayCountriesCount self.maxGiveawayPeriodSeconds = maxGiveawayPeriodSeconds - self.minChannelNameColorLevel = minChannelNameColorLevel } } } @@ -174,8 +171,7 @@ public extension EngineConfiguration.UserLimits { maxStoriesSuggestedReactions: userLimitsConfiguration.maxStoriesSuggestedReactions, maxGiveawayChannelsCount: userLimitsConfiguration.maxGiveawayChannelsCount, maxGiveawayCountriesCount: userLimitsConfiguration.maxGiveawayCountriesCount, - maxGiveawayPeriodSeconds: userLimitsConfiguration.maxGiveawayPeriodSeconds, - minChannelNameColorLevel: userLimitsConfiguration.minChannelNameColorLevel + maxGiveawayPeriodSeconds: userLimitsConfiguration.maxGiveawayPeriodSeconds ) } } diff --git a/submodules/TelegramUI/BUILD b/submodules/TelegramUI/BUILD index d551796d019..69f53797f10 100644 --- a/submodules/TelegramUI/BUILD +++ b/submodules/TelegramUI/BUILD @@ -19,12 +19,13 @@ NGDEPS = [ "//Nicegram/NGStats:NGStats", "//Nicegram/NGModels:NGModels", "@FirebaseSDK//:FirebaseCrashlytics", + "@swiftpkg_nicegram_assistant_ios//:Sources_FeatChatBanner", "@swiftpkg_nicegram_assistant_ios//:Sources_FeatPartners", + "@swiftpkg_nicegram_assistant_ios//:Sources_FeatPremium", "@swiftpkg_nicegram_assistant_ios//:Sources_NGAssistantUI", "@swiftpkg_nicegram_assistant_ios//:Sources_NGAuth", "@swiftpkg_nicegram_assistant_ios//:Sources_NGCardUI", "@swiftpkg_nicegram_assistant_ios//:Sources_NGEntryPoint", - "@swiftpkg_nicegram_assistant_ios//:Sources_NGPremium", "@swiftpkg_nicegram_assistant_ios//:Sources__NGRemoteConfig", ] diff --git a/submodules/TelegramUI/Components/Chat/ChatChannelSubscriberInputPanelNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatChannelSubscriberInputPanelNode/BUILD index cc26e420ef0..ea86d5a2029 100644 --- a/submodules/TelegramUI/Components/Chat/ChatChannelSubscriberInputPanelNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatChannelSubscriberInputPanelNode/BUILD @@ -1,5 +1,9 @@ load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") +NGDEPS = [ + "@swiftpkg_nicegram_assistant_ios//:Sources_FeatTasks", +] + swift_library( name = "ChatChannelSubscriberInputPanelNode", module_name = "ChatChannelSubscriberInputPanelNode", @@ -9,7 +13,7 @@ swift_library( copts = [ "-warnings-as-errors", ], - deps = [ + deps = NGDEPS + [ "//submodules/AsyncDisplayKit", "//submodules/Display", "//submodules/TelegramCore", diff --git a/submodules/TelegramUI/Components/Chat/ChatChannelSubscriberInputPanelNode/Sources/ChatChannelSubscriberInputPanelNode.swift b/submodules/TelegramUI/Components/Chat/ChatChannelSubscriberInputPanelNode/Sources/ChatChannelSubscriberInputPanelNode.swift index cc5bd993c50..787e2780023 100644 --- a/submodules/TelegramUI/Components/Chat/ChatChannelSubscriberInputPanelNode/Sources/ChatChannelSubscriberInputPanelNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatChannelSubscriberInputPanelNode/Sources/ChatChannelSubscriberInputPanelNode.swift @@ -1,3 +1,6 @@ +// MARK: Nicegram Tasks +import FeatTasks +// import Foundation import UIKit import AsyncDisplayKit @@ -225,6 +228,18 @@ public final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode { strongSelf.isJoining = false } } + + // MARK: Nicegram Tasks + if #available(iOS 15.0, *) { + Task { + let tryCompleteOngoingSubscribeTaskUseCase = TasksContainer.shared.tryCompleteOngoingSubscribeTaskUseCase() + + await tryCompleteOngoingSubscribeTaskUseCase( + channelId: peer.addressName ?? "" + ) + } + } + // }).startStrict(error: { [weak self] error in guard let strongSelf = self, let presentationInterfaceState = strongSelf.presentationInterfaceState, let peer = presentationInterfaceState.renderedPeer?.peer else { return diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentButtonNode/Sources/ChatMessageAttachedContentButtonNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentButtonNode/Sources/ChatMessageAttachedContentButtonNode.swift index 07d7945ac31..ecb14a0075f 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentButtonNode/Sources/ChatMessageAttachedContentButtonNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentButtonNode/Sources/ChatMessageAttachedContentButtonNode.swift @@ -7,7 +7,7 @@ import ChatPresentationInterfaceState import ShimmerEffect private let buttonFont = Font.semibold(14.0) -private let sharedBackgroundImage = generateStretchableFilledCircleImage(radius: 4.0, color: UIColor.white)?.withRenderingMode(.alwaysTemplate) +private let sharedBackgroundImage = generateStretchableFilledCircleImage(radius: 6.0, color: UIColor.white)?.withRenderingMode(.alwaysTemplate) public final class ChatMessageAttachedContentButtonNode: HighlightTrackingButtonNode { private let textNode: TextNode @@ -60,7 +60,7 @@ public final class ChatMessageAttachedContentButtonNode: HighlightTrackingButton shimmerEffectNode = current } else { shimmerEffectNode = ShimmerEffectForegroundNode() - shimmerEffectNode.cornerRadius = 5.0 + shimmerEffectNode.cornerRadius = 6.0 self.insertSubnode(shimmerEffectNode, at: 0) self.shimmerEffectNode = shimmerEffectNode } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode/Sources/ChatMessageAttachedContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode/Sources/ChatMessageAttachedContentNode.swift index 63e599cd454..9e04fd426ed 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode/Sources/ChatMessageAttachedContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode/Sources/ChatMessageAttachedContentNode.swift @@ -850,6 +850,11 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode { } } + if let _ = message.adAttribute { + actualSize.height += 2.0 + backgroundInsets.bottom += 2.0 + } + return (actualSize, { animation, synchronousLoads, applyInfo in guard let self else { return @@ -1214,890 +1219,6 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode { }) }) }) - - /*var horizontalInsets = UIEdgeInsets(top: 0.0, left: 10.0, bottom: 0.0, right: 10.0) - if displayLine { - horizontalInsets.left += 10.0 - horizontalInsets.right += 9.0 - } - - var titleBeforeMedia = false - var preferMediaBeforeText = false - var preferMediaAspectFilled = false - if let (_, flags) = mediaAndFlags { - preferMediaBeforeText = flags.contains(.preferMediaBeforeText) - preferMediaAspectFilled = flags.contains(.preferMediaAspectFilled) - titleBeforeMedia = flags.contains(.titleBeforeMedia) - } - - var contentMode: InteractiveMediaNodeContentMode = preferMediaAspectFilled ? .aspectFill : .aspectFit - - var edited = false - if attributes.updatingMedia != nil { - edited = true - } - var viewCount: Int? - var dateReplies = 0 - var dateReactionsAndPeers = mergedMessageReactionsAndPeers(accountPeer: associatedData.accountPeer, message: message) - if message.isRestricted(platform: "ios", contentSettings: context.currentContentSettings.with { $0 }) { - dateReactionsAndPeers = ([], []) - } - for attribute in 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? ReplyThreadMessageAttribute, case .peer = chatLocation { - if let channel = message.peers[message.id.peerId] as? TelegramChannel, case .group = channel.info { - dateReplies = Int(attribute.count) - } - } - } - - let dateText = stringForMessageTimestampStatus(accountPeerId: context.account.peerId, message: message, dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, strings: presentationData.strings, associatedData: associatedData) - - var webpageGalleryMediaCount: Int? - for media in message.media { - if let media = media as? TelegramMediaWebpage { - if case let .Loaded(content) = media.content, let instantPage = content.instantPage, let image = content.image { - switch instantPageType(of: content) { - case .album: - let count = instantPageGalleryMedia(webpageId: media.webpageId, page: instantPage, galleryMedia: image).count - if count > 1 { - webpageGalleryMediaCount = count - } - default: - break - } - } - } - } - - var textString: NSAttributedString? - var inlineImageDimensions: CGSize? - var inlineImageSize: CGSize? - var updateInlineImageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>? - var textCutout = TextNodeCutout() - var initialWidth: CGFloat = CGFloat.greatestFiniteMagnitude - var refineContentImageLayout: ((CGSize, Bool, Bool, ImageCorners) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> ChatMessageInteractiveMediaNode)))? - var refineContentFileLayout: ((CGSize) -> (CGFloat, (CGFloat) -> (CGSize, (Bool, ListViewItemUpdateAnimation, ListViewItemApply?) -> ChatMessageInteractiveFileNode)))? - - var contentInstantVideoSizeAndApply: (ChatMessageInstantVideoItemLayoutResult, (ChatMessageInstantVideoItemLayoutData, ListViewItemUpdateAnimation) -> ChatMessageInteractiveInstantVideoNode)? - - let topTitleString = NSMutableAttributedString() - - let string = NSMutableAttributedString() - var notEmpty = false - - let messageTheme = incoming ? presentationData.theme.theme.chat.message.incoming : presentationData.theme.theme.chat.message.outgoing - - if let title = title, !title.isEmpty { - if titleBeforeMedia { - topTitleString.append(NSAttributedString(string: title, font: titleFont, textColor: messageTheme.accentTextColor)) - } else { - string.append(NSAttributedString(string: title, font: titleFont, textColor: messageTheme.accentTextColor)) - notEmpty = true - } - } - - if let subtitle = subtitle, subtitle.length > 0 { - if notEmpty { - string.append(NSAttributedString(string: "\n", font: textFont, textColor: messageTheme.primaryTextColor)) - } - let updatedSubtitle = NSMutableAttributedString() - updatedSubtitle.append(subtitle) - updatedSubtitle.addAttribute(.foregroundColor, value: messageTheme.primaryTextColor, range: NSMakeRange(0, subtitle.length)) - updatedSubtitle.addAttribute(.font, value: titleFont, range: NSMakeRange(0, subtitle.length)) - string.append(updatedSubtitle) - notEmpty = true - } - - if let text = text, !text.isEmpty { - if notEmpty { - string.append(NSAttributedString(string: "\n", font: textFont, textColor: messageTheme.primaryTextColor)) - } - if let entities = entities { - string.append(stringWithAppliedEntities(text, entities: entities, baseColor: messageTheme.primaryTextColor, linkColor: messageTheme.linkTextColor, baseFont: textFont, linkFont: textFont, boldFont: textBoldFont, italicFont: textItalicFont, boldItalicFont: textBoldItalicFont, fixedFont: textFixedFont, blockQuoteFont: textBlockQuoteFont, message: nil, adjustQuoteFontSize: true)) - } else { - string.append(NSAttributedString(string: text + "\n", font: textFont, textColor: messageTheme.primaryTextColor)) - } - notEmpty = true - } - - textString = string - if string.length > 1000 { - textString = string.attributedSubstring(from: NSMakeRange(0, 1000)) - } - - var isReplyThread = false - if case .replyThread = chatLocation { - isReplyThread = true - } - - var skipStandardStatus = false - var isImage = false - var isFile = false - - var automaticPlayback = false - - var textStatusType: ChatMessageDateAndStatusType? - var imageStatusType: ChatMessageDateAndStatusType? - var additionalImageBadgeContent: ChatMessageInteractiveMediaBadgeContent? - - if let (media, flags) = mediaAndFlags { - if let file = media as? TelegramMediaFile { - if file.mimeType == "application/x-tgtheme-ios", let size = file.size, size < 16 * 1024 { - isImage = true - } else if file.isInstantVideo { - isImage = true - } else if file.isVideo { - isImage = true - } else if file.isSticker || file.isAnimatedSticker { - isImage = true - } else { - isFile = true - } - } else if let _ = media as? TelegramMediaImage { - if !flags.contains(.preferMediaInline) { - isImage = true - } - } else if let _ = media as? TelegramMediaWebFile { - isImage = true - } else if let _ = media as? WallpaperPreviewMedia { - isImage = true - } else if let _ = media as? TelegramMediaStory { - isImage = true - } - } - - if preferMediaBeforeText, let textString, textString.length != 0 { - isImage = false - } - - var statusInText = !isImage - if let textString { - if textString.length == 0 { - statusInText = false - } - } else { - statusInText = false - } - - switch preparePosition { - case .linear(_, .None), .linear(_, .Neighbour(true, _, _)): - if let count = webpageGalleryMediaCount { - additionalImageBadgeContent = .text(inset: 0.0, backgroundColor: presentationData.theme.theme.chat.message.mediaDateAndStatusFillColor, foregroundColor: presentationData.theme.theme.chat.message.mediaDateAndStatusTextColor, text: NSAttributedString(string: presentationData.strings.Items_NOfM("1", "\(count)").string), iconName: nil) - skipStandardStatus = isImage - } else if let mediaBadge = mediaBadge { - additionalImageBadgeContent = .text(inset: 0.0, backgroundColor: presentationData.theme.theme.chat.message.mediaDateAndStatusFillColor, foregroundColor: presentationData.theme.theme.chat.message.mediaDateAndStatusTextColor, text: NSAttributedString(string: mediaBadge), iconName: nil) - } else { - skipStandardStatus = isFile - } - - if !skipStandardStatus { - if incoming { - if isImage { - imageStatusType = .ImageIncoming - } else { - textStatusType = .BubbleIncoming - } - } else { - if message.flags.contains(.Failed) { - if isImage { - imageStatusType = .ImageOutgoing(.Failed) - } else { - textStatusType = .BubbleOutgoing(.Failed) - } - } else if (message.flags.isSending && !message.isSentOrAcknowledged) || attributes.updatingMedia != nil { - if isImage { - imageStatusType = .ImageOutgoing(.Sending) - } else { - textStatusType = .BubbleOutgoing(.Sending) - } - } else { - if isImage { - imageStatusType = .ImageOutgoing(.Sent(read: messageRead)) - } else { - textStatusType = .BubbleOutgoing(.Sent(read: messageRead)) - } - } - } - } - default: - break - } - - let imageDateAndStatus = imageStatusType.flatMap { statusType -> ChatMessageDateAndStatus in - ChatMessageDateAndStatus( - type: statusType, - edited: edited, - viewCount: viewCount, - dateReactions: dateReactionsAndPeers.reactions, - dateReactionPeers: dateReactionsAndPeers.peers, - dateReplies: dateReplies, - isPinned: message.tags.contains(.pinned) && !associatedData.isInPinnedListMode && !isReplyThread, - dateText: dateText - ) - } - - if let (media, flags) = mediaAndFlags { - if let file = media as? TelegramMediaFile { - if file.mimeType == "application/x-tgtheme-ios", let size = file.size, size < 16 * 1024 { - let (_, initialImageWidth, refineLayout) = contentImageLayout(context, presentationData, presentationData.dateTimeFormat, message, associatedData, attributes, file, imageDateAndStatus, .full, associatedData.automaticDownloadPeerType, associatedData.automaticDownloadPeerId, .constrained(CGSize(width: constrainedSize.width - horizontalInsets.left - horizontalInsets.right, height: constrainedSize.height)), layoutConstants, contentMode, controllerInteraction.presentationContext) - initialWidth = initialImageWidth + horizontalInsets.left + horizontalInsets.right - refineContentImageLayout = refineLayout - } else if file.isInstantVideo { - let displaySize = CGSize(width: 212.0, height: 212.0) - let automaticDownload = shouldDownloadMediaAutomatically(settings: automaticDownloadSettings, peerType: associatedData.automaticDownloadPeerType, networkType: associatedData.automaticDownloadNetworkType, authorPeerId: message.author?.id, contactsPeerIds: associatedData.contactsPeerIds, media: file) - let (videoLayout, apply) = contentInstantVideoLayout(ChatMessageBubbleContentItem(context: context, controllerInteraction: controllerInteraction, message: message, topMessage: message, read: messageRead, chatLocation: chatLocation, presentationData: presentationData, associatedData: associatedData, attributes: attributes, isItemPinned: message.tags.contains(.pinned) && !isReplyThread, isItemEdited: false), constrainedSize.width - horizontalInsets.left - horizontalInsets.right, displaySize, displaySize, 0.0, .bubble, automaticDownload, 0.0) - initialWidth = videoLayout.contentSize.width + videoLayout.overflowLeft + videoLayout.overflowRight - contentInstantVideoSizeAndApply = (videoLayout, apply) - } else if file.isVideo { - var automaticDownload: InteractiveMediaNodeAutodownloadMode = .none - - if shouldDownloadMediaAutomatically(settings: automaticDownloadSettings, peerType: associatedData.automaticDownloadPeerType, networkType: associatedData.automaticDownloadNetworkType, authorPeerId: message.author?.id, contactsPeerIds: associatedData.contactsPeerIds, media: file) { - automaticDownload = .full - } else if shouldPredownloadMedia(settings: automaticDownloadSettings, peerType: associatedData.automaticDownloadPeerType, networkType: associatedData.automaticDownloadNetworkType, media: file) { - automaticDownload = .prefetch - } - if file.isAnimated { - automaticPlayback = context.sharedContext.energyUsageSettings.autoplayGif - } else if file.isVideo && context.sharedContext.energyUsageSettings.autoplayVideo { - var willDownloadOrLocal = false - if case .full = automaticDownload { - willDownloadOrLocal = true - } else { - willDownloadOrLocal = context.account.postbox.mediaBox.completedResourcePath(file.resource) != nil - } - if willDownloadOrLocal { - automaticPlayback = true - contentMode = .aspectFill - } - } - - let (_, initialImageWidth, refineLayout) = contentImageLayout(context, presentationData, presentationData.dateTimeFormat, message, associatedData, attributes, file, imageDateAndStatus, automaticDownload, associatedData.automaticDownloadPeerType, associatedData.automaticDownloadPeerId, .constrained(CGSize(width: constrainedSize.width - horizontalInsets.left - horizontalInsets.right, height: constrainedSize.height)), layoutConstants, contentMode, controllerInteraction.presentationContext) - initialWidth = initialImageWidth + horizontalInsets.left + horizontalInsets.right - refineContentImageLayout = refineLayout - } else if file.isSticker || file.isAnimatedSticker { - let automaticDownload = shouldDownloadMediaAutomatically(settings: automaticDownloadSettings, peerType: associatedData.automaticDownloadPeerType, networkType: associatedData.automaticDownloadNetworkType, authorPeerId: message.author?.id, contactsPeerIds: associatedData.contactsPeerIds, media: file) - let (_, initialImageWidth, refineLayout) = contentImageLayout(context, presentationData, presentationData.dateTimeFormat, message, associatedData, attributes, file, imageDateAndStatus, automaticDownload ? .full : .none, associatedData.automaticDownloadPeerType, associatedData.automaticDownloadPeerId, .constrained(CGSize(width: constrainedSize.width - horizontalInsets.left - horizontalInsets.right, height: constrainedSize.height)), layoutConstants, contentMode, controllerInteraction.presentationContext) - initialWidth = initialImageWidth + horizontalInsets.left + horizontalInsets.right - refineContentImageLayout = refineLayout - } else { - let automaticDownload = shouldDownloadMediaAutomatically(settings: automaticDownloadSettings, peerType: associatedData.automaticDownloadPeerType, networkType: associatedData.automaticDownloadNetworkType, authorPeerId: message.author?.id, contactsPeerIds: associatedData.contactsPeerIds, media: file) - - let statusType: ChatMessageDateAndStatusType - if incoming { - statusType = .BubbleIncoming - } else { - if message.flags.contains(.Failed) { - statusType = .BubbleOutgoing(.Failed) - } else if (message.flags.isSending && !message.isSentOrAcknowledged) || attributes.updatingMedia != nil { - statusType = .BubbleOutgoing(.Sending) - } else { - statusType = .BubbleOutgoing(.Sent(read: messageRead)) - } - } - - let (_, refineLayout) = contentFileLayout(ChatMessageInteractiveFileNode.Arguments( - context: context, - presentationData: presentationData, - message: message, - topMessage: message, - associatedData: associatedData, - chatLocation: chatLocation, - attributes: attributes, - isPinned: message.tags.contains(.pinned) && !associatedData.isInPinnedListMode && !isReplyThread, - forcedIsEdited: false, - file: file, - automaticDownload: automaticDownload, - incoming: incoming, - isRecentActions: false, - forcedResourceStatus: associatedData.forcedResourceStatus, - dateAndStatusType: statusType, - displayReactions: false, - messageSelection: nil, - layoutConstants: layoutConstants, - constrainedSize: CGSize(width: constrainedSize.width - horizontalInsets.left - horizontalInsets.right, height: constrainedSize.height), - controllerInteraction: controllerInteraction - )) - refineContentFileLayout = refineLayout - } - } else if let image = media as? TelegramMediaImage { - if !flags.contains(.preferMediaInline) { - let automaticDownload = shouldDownloadMediaAutomatically(settings: automaticDownloadSettings, peerType: associatedData.automaticDownloadPeerType, networkType: associatedData.automaticDownloadNetworkType, authorPeerId: message.author?.id, contactsPeerIds: associatedData.contactsPeerIds, media: image) - let (_, initialImageWidth, refineLayout) = contentImageLayout(context, presentationData, presentationData.dateTimeFormat, message, associatedData, attributes, image, imageDateAndStatus, automaticDownload ? .full : .none, associatedData.automaticDownloadPeerType, associatedData.automaticDownloadPeerId, .constrained(CGSize(width: constrainedSize.width - horizontalInsets.left - horizontalInsets.right, height: constrainedSize.height)), layoutConstants, contentMode, controllerInteraction.presentationContext) - initialWidth = initialImageWidth + horizontalInsets.left + horizontalInsets.right - refineContentImageLayout = refineLayout - } else if let dimensions = largestImageRepresentation(image.representations)?.dimensions { - inlineImageDimensions = dimensions.cgSize - - if image != currentImage || !currentMediaIsInline { - updateInlineImageSignal = chatWebpageSnippetPhoto(account: context.account, userLocation: .peer(message.id.peerId), photoReference: .message(message: MessageReference(message), media: image)) - } - } - } else if let image = media as? TelegramMediaWebFile { - let automaticDownload = shouldDownloadMediaAutomatically(settings: automaticDownloadSettings, peerType: associatedData.automaticDownloadPeerType, networkType: associatedData.automaticDownloadNetworkType, authorPeerId: message.author?.id, contactsPeerIds: associatedData.contactsPeerIds, media: image) - let (_, initialImageWidth, refineLayout) = contentImageLayout(context, presentationData, presentationData.dateTimeFormat, message, associatedData, attributes, image, imageDateAndStatus, automaticDownload ? .full : .none, associatedData.automaticDownloadPeerType, associatedData.automaticDownloadPeerId, .constrained(CGSize(width: constrainedSize.width - horizontalInsets.left - horizontalInsets.right, height: constrainedSize.height)), layoutConstants, contentMode, controllerInteraction.presentationContext) - initialWidth = initialImageWidth + horizontalInsets.left + horizontalInsets.right - refineContentImageLayout = refineLayout - } else if let wallpaper = media as? WallpaperPreviewMedia { - let (_, initialImageWidth, refineLayout) = contentImageLayout(context, presentationData, presentationData.dateTimeFormat, message, associatedData, attributes, wallpaper, imageDateAndStatus, .full, associatedData.automaticDownloadPeerType, associatedData.automaticDownloadPeerId, .constrained(CGSize(width: constrainedSize.width - horizontalInsets.left - horizontalInsets.right, height: constrainedSize.height)), layoutConstants, contentMode, controllerInteraction.presentationContext) - initialWidth = initialImageWidth + horizontalInsets.left + horizontalInsets.right - refineContentImageLayout = refineLayout - if case let .file(_, _, _, _, isTheme, _) = wallpaper.content, isTheme { - skipStandardStatus = true - } - } else if let story = media as? TelegramMediaStory { - var media: Media? - if let storyValue = message.associatedStories[story.storyId]?.get(Stories.StoredItem.self), case let .item(item) = storyValue { - media = item.media - } - - var automaticDownload = false - if let media { - automaticDownload = shouldDownloadMediaAutomatically(settings: automaticDownloadSettings, peerType: associatedData.automaticDownloadPeerType, networkType: associatedData.automaticDownloadNetworkType, authorPeerId: message.author?.id, contactsPeerIds: associatedData.contactsPeerIds, media: media) - } - - let (_, initialImageWidth, refineLayout) = contentImageLayout(context, presentationData, presentationData.dateTimeFormat, message, associatedData, attributes, story, imageDateAndStatus, automaticDownload ? .full : .none, associatedData.automaticDownloadPeerType, associatedData.automaticDownloadPeerId, .constrained(CGSize(width: constrainedSize.width - horizontalInsets.left - horizontalInsets.right, height: constrainedSize.height)), layoutConstants, contentMode, controllerInteraction.presentationContext) - initialWidth = initialImageWidth + horizontalInsets.left + horizontalInsets.right - refineContentImageLayout = refineLayout - } - } - - if let _ = inlineImageDimensions { - inlineImageSize = CGSize(width: 53.0, height: 53.0) - - if let inlineImageSize = inlineImageSize { - textCutout.topRight = CGSize(width: inlineImageSize.width + 10.0, height: inlineImageSize.height + 10.0) - } - } - - return (initialWidth, { constrainedSize, position in - var insets = UIEdgeInsets(top: 0.0, left: horizontalInsets.left, bottom: 0.0, right: horizontalInsets.right) - - switch position { - case let .linear(topNeighbor, bottomNeighbor): - switch topNeighbor { - case .None: - insets.top += 10.0 - default: - break - } - switch bottomNeighbor { - case .None: - insets.bottom += 12.0 - default: - insets.bottom += 0.0 - } - default: - break - } - - let textConstrainedSize = CGSize(width: constrainedSize.width - insets.left - insets.right, height: constrainedSize.height - insets.top - insets.bottom) - - var updatedAdditionalImageBadge: ChatMessageInteractiveMediaBadge? - if let _ = additionalImageBadgeContent { - updatedAdditionalImageBadge = currentAdditionalImageBadgeNode ?? ChatMessageInteractiveMediaBadge() - } - - let upatedTextCutout = textCutout - - - let (topTitleLayout, topTitleApply) = topTitleAsyncLayout(TextNodeLayoutArguments(attributedString: topTitleString, backgroundColor: nil, maximumNumberOfLines: 12, truncationType: .end, constrainedSize: textConstrainedSize, alignment: .natural, cutout: nil, insets: UIEdgeInsets())) - let (textLayout, textApply) = textAsyncLayout(TextNodeLayoutArguments(attributedString: textString, backgroundColor: nil, maximumNumberOfLines: 12, truncationType: .end, constrainedSize: textConstrainedSize, alignment: .natural, cutout: upatedTextCutout, insets: UIEdgeInsets())) - - var statusSuggestedWidthAndContinue: (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> Void))? - if statusInText, let textStatusType = textStatusType { - let trailingContentWidth: CGFloat - if textLayout.hasRTL { - trailingContentWidth = 10000.0 - } else { - trailingContentWidth = textLayout.trailingLineWidth - } - statusSuggestedWidthAndContinue = statusLayout(ChatMessageDateAndStatusNode.Arguments( - context: context, - presentationData: presentationData, - edited: edited, - impressionCount: viewCount, - dateText: dateText, - type: textStatusType, - layoutInput: .trailingContent(contentWidth: trailingContentWidth, reactionSettings: shouldDisplayInlineDateReactions(message: message, isPremium: associatedData.isPremium, forceInline: associatedData.forceInlineReactions) ? ChatMessageDateAndStatusNode.TrailingReactionSettings(displayInline: true, preferAdditionalInset: false) : nil), - constrainedSize: textConstrainedSize, - availableReactions: associatedData.availableReactions, - reactions: dateReactionsAndPeers.reactions, - reactionPeers: dateReactionsAndPeers.peers, - displayAllReactionPeers: message.id.peerId.namespace == Namespaces.Peer.CloudUser, - replyCount: dateReplies, - isPinned: message.tags.contains(.pinned) && !associatedData.isInPinnedListMode && !isReplyThread, - hasAutoremove: message.isSelfExpiring, - canViewReactionList: canViewMessageReactionList(message: message), - animationCache: controllerInteraction.presentationContext.animationCache, - animationRenderer: controllerInteraction.presentationContext.animationRenderer - )) - } - let _ = statusSuggestedWidthAndContinue - - var textFrame = CGRect(origin: CGPoint(), size: textLayout.size) - - textFrame = textFrame.offsetBy(dx: insets.left, dy: insets.top) - - let mainColor: UIColor - if !incoming { - mainColor = presentationData.theme.theme.chat.message.outgoing.accentTextColor - } else { - var authorNameColor: UIColor? - let author = message.author - if [Namespaces.Peer.CloudGroup, Namespaces.Peer.CloudChannel].contains(message.id.peerId.namespace), author?.id.namespace == Namespaces.Peer.CloudUser { - authorNameColor = author.flatMap { chatMessagePeerIdColors[Int(clamping: $0.id.id._internalGetInt64Value() % 7)] } - if let rawAuthorNameColor = authorNameColor { - var dimColors = false - switch presentationData.theme.theme.name { - case .builtin(.nightAccent), .builtin(.night): - dimColors = true - default: - break - } - if dimColors { - var hue: CGFloat = 0.0 - var saturation: CGFloat = 0.0 - var brightness: CGFloat = 0.0 - rawAuthorNameColor.getHue(&hue, saturation: &saturation, brightness: &brightness, alpha: nil) - authorNameColor = UIColor(hue: hue, saturation: saturation * 0.7, brightness: min(1.0, brightness * 1.2), alpha: 1.0) - } - } - } - - if let authorNameColor { - mainColor = authorNameColor - } else { - mainColor = presentationData.theme.theme.chat.message.incoming.accentTextColor - } - } - - var boundingSize = textFrame.size - if titleBeforeMedia { - boundingSize.height += topTitleLayout.size.height + 4.0 - boundingSize.width = max(boundingSize.width, topTitleLayout.size.width) - } - if let inlineImageSize = inlineImageSize { - if boundingSize.height < inlineImageSize.height { - boundingSize.height = inlineImageSize.height - } - } - - if let statusSuggestedWidthAndContinue = statusSuggestedWidthAndContinue { - boundingSize.width = max(boundingSize.width, statusSuggestedWidthAndContinue.0) - } - - var finalizeContentImageLayout: ((CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> ChatMessageInteractiveMediaNode))? - if let refineContentImageLayout = refineContentImageLayout { - let (refinedWidth, finalizeImageLayout) = refineContentImageLayout(textConstrainedSize, automaticPlayback, true, ImageCorners(radius: 4.0)) - finalizeContentImageLayout = finalizeImageLayout - - boundingSize.width = max(boundingSize.width, refinedWidth) - } - var finalizeContentFileLayout: ((CGFloat) -> (CGSize, (Bool, ListViewItemUpdateAnimation, ListViewItemApply?) -> ChatMessageInteractiveFileNode))? - if let refineContentFileLayout = refineContentFileLayout { - let (refinedWidth, finalizeFileLayout) = refineContentFileLayout(textConstrainedSize) - finalizeContentFileLayout = finalizeFileLayout - - boundingSize.width = max(boundingSize.width, refinedWidth) - } - - if let (videoLayout, _) = contentInstantVideoSizeAndApply { - boundingSize.width = max(boundingSize.width, videoLayout.contentSize.width + videoLayout.overflowLeft + videoLayout.overflowRight) - } - - var imageApply: (() -> Void)? - if let inlineImageSize = inlineImageSize, let inlineImageDimensions = inlineImageDimensions { - let imageCorners = ImageCorners(topLeft: .Corner(4.0), topRight: .Corner(4.0), bottomLeft: .Corner(4.0), bottomRight: .Corner(4.0)) - let arguments = TransformImageArguments(corners: imageCorners, imageSize: inlineImageDimensions.aspectFilled(inlineImageSize), boundingSize: inlineImageSize, intrinsicInsets: UIEdgeInsets(), emptyColor: incoming ? presentationData.theme.theme.chat.message.incoming.mediaPlaceholderColor : presentationData.theme.theme.chat.message.outgoing.mediaPlaceholderColor) - imageApply = imageLayout(arguments) - } - - var continueActionButtonLayout: ((CGFloat, CGFloat) -> (CGSize, () -> ChatMessageAttachedContentButtonNode))? - if let actionTitle = actionTitle, !isPreview { - var buttonIconImage: UIImage? - var buttonHighlightedIconImage: UIImage? - var cornerIcon = false - let titleColor: UIColor - let titleHighlightedColor: UIColor - if incoming { - if let actionIcon { - switch actionIcon { - case .instant: - buttonIconImage = PresentationResourcesChat.chatMessageAttachedContentButtonIconInstantIncoming(presentationData.theme.theme)! - buttonHighlightedIconImage = PresentationResourcesChat.chatMessageAttachedContentHighlightedButtonIconInstantIncoming(presentationData.theme.theme, wallpaper: !presentationData.theme.wallpaper.isEmpty)! - case .link: - buttonIconImage = PresentationResourcesChat.chatMessageAttachedContentButtonIconLinkIncoming(presentationData.theme.theme)! - buttonHighlightedIconImage = PresentationResourcesChat.chatMessageAttachedContentHighlightedButtonIconLinkIncoming(presentationData.theme.theme, wallpaper: !presentationData.theme.wallpaper.isEmpty)! - cornerIcon = true - } - } - titleColor = presentationData.theme.theme.chat.message.incoming.accentTextColor - let bubbleColor = bubbleColorComponents(theme: presentationData.theme.theme, incoming: true, wallpaper: !presentationData.theme.wallpaper.isEmpty) - titleHighlightedColor = bubbleColor.fill[0] - } else { - if let actionIcon { - switch actionIcon { - case .instant: - buttonIconImage = PresentationResourcesChat.chatMessageAttachedContentButtonIconInstantOutgoing(presentationData.theme.theme)! - buttonHighlightedIconImage = PresentationResourcesChat.chatMessageAttachedContentHighlightedButtonIconInstantOutgoing(presentationData.theme.theme, wallpaper: !presentationData.theme.wallpaper.isEmpty)! - case .link: - buttonIconImage = PresentationResourcesChat.chatMessageAttachedContentButtonIconLinkOutgoing(presentationData.theme.theme)! - buttonHighlightedIconImage = PresentationResourcesChat.chatMessageAttachedContentHighlightedButtonIconLinkOutgoing(presentationData.theme.theme, wallpaper: !presentationData.theme.wallpaper.isEmpty)! - cornerIcon = true - } - } - titleColor = presentationData.theme.theme.chat.message.outgoing.accentTextColor - let bubbleColor = bubbleColorComponents(theme: presentationData.theme.theme, incoming: false, wallpaper: !presentationData.theme.wallpaper.isEmpty) - titleHighlightedColor = bubbleColor.fill[0] - } - let (buttonWidth, continueLayout) = makeButtonLayout(constrainedSize.width, nil, nil, buttonIconImage, buttonHighlightedIconImage, cornerIcon, actionTitle, titleColor, titleHighlightedColor, false) - boundingSize.width = max(buttonWidth, boundingSize.width) - continueActionButtonLayout = continueLayout - } - - boundingSize.width += insets.left + insets.right - boundingSize.height += insets.top + insets.bottom - - return (boundingSize.width, { boundingWidth in - var adjustedBoundingSize = boundingSize - - var imageFrame: CGRect? - if let inlineImageSize = inlineImageSize { - imageFrame = CGRect(origin: CGPoint(x: boundingWidth - inlineImageSize.width - insets.right + 4.0, y: 0.0), size: inlineImageSize) - } - - var contentImageSizeAndApply: (CGSize, (ListViewItemUpdateAnimation, Bool) -> ChatMessageInteractiveMediaNode)? - if let finalizeContentImageLayout = finalizeContentImageLayout { - let (size, apply) = finalizeContentImageLayout(boundingWidth - insets.left - insets.right) - contentImageSizeAndApply = (size, apply) - - var imageHeightAddition = size.height - if textFrame.size.height > CGFloat.ulpOfOne { - imageHeightAddition += 2.0 - } - - adjustedBoundingSize.height += imageHeightAddition + 7.0 - } - - var contentFileSizeAndApply: (CGSize, (Bool, ListViewItemUpdateAnimation, ListViewItemApply?) -> ChatMessageInteractiveFileNode)? - if let finalizeContentFileLayout = finalizeContentFileLayout { - let (size, apply) = finalizeContentFileLayout(boundingWidth - insets.left - insets.right) - contentFileSizeAndApply = (size, apply) - - var imageHeightAddition = size.height + 6.0 - if textFrame.size.height > CGFloat.ulpOfOne { - imageHeightAddition += 6.0 - } else { - imageHeightAddition += 7.0 - } - - adjustedBoundingSize.height += imageHeightAddition + 5.0 - } - - if let (videoLayout, _) = contentInstantVideoSizeAndApply { - let imageHeightAddition = videoLayout.contentSize.height + 6.0 - - adjustedBoundingSize.height += imageHeightAddition// + 5.0 - } - - var actionButtonSizeAndApply: ((CGSize, () -> ChatMessageAttachedContentButtonNode))? - if let continueActionButtonLayout = continueActionButtonLayout { - let (size, apply) = continueActionButtonLayout(boundingWidth - 5.0 - insets.right, 38.0) - actionButtonSizeAndApply = (size, apply) - adjustedBoundingSize.height += 4.0 + size.height - if let text, !text.isEmpty { - if contentImageSizeAndApply == nil { - adjustedBoundingSize.height += 5.0 - } else if let (_, flags) = mediaAndFlags, flags.contains(.preferMediaBeforeText) { - adjustedBoundingSize.height += 5.0 - } - } - } - - var statusSizeAndApply: ((CGSize), (ListViewItemUpdateAnimation) -> Void)? - if let statusSuggestedWidthAndContinue = statusSuggestedWidthAndContinue { - statusSizeAndApply = statusSuggestedWidthAndContinue.1(boundingWidth - insets.left - insets.right) - } - if let statusSizeAndApply = statusSizeAndApply { - adjustedBoundingSize.height += statusSizeAndApply.0.height - - if let imageFrame = imageFrame, statusSizeAndApply.0.height == 0.0 { - if statusInText { - adjustedBoundingSize.height = max(adjustedBoundingSize.height, imageFrame.maxY + 8.0 + 15.0) - } - } - } - - adjustedBoundingSize.width = max(boundingWidth, adjustedBoundingSize.width) - - var contentMediaHeight: CGFloat? - if let (contentImageSize, _) = contentImageSizeAndApply { - contentMediaHeight = contentImageSize.height - } - - if let (contentFileSize, _) = contentFileSizeAndApply { - contentMediaHeight = contentFileSize.height - } - - if let (videoLayout, _) = contentInstantVideoSizeAndApply { - contentMediaHeight = videoLayout.contentSize.height - } - - var textVerticalOffset: CGFloat = 0.0 - if titleBeforeMedia { - textVerticalOffset += topTitleLayout.size.height + 4.0 - } - if let contentMediaHeight = contentMediaHeight, let (_, flags) = mediaAndFlags, flags.contains(.preferMediaBeforeText) { - textVerticalOffset += contentMediaHeight + 7.0 - } - let adjustedTextFrame = textFrame.offsetBy(dx: 0.0, dy: textVerticalOffset) - - var statusFrame: CGRect? - if let statusSizeAndApply = statusSizeAndApply { - var finalStatusFrame = CGRect(origin: CGPoint(x: adjustedTextFrame.minX, y: adjustedTextFrame.maxY), size: statusSizeAndApply.0) - if let imageFrame = imageFrame { - if finalStatusFrame.maxY < imageFrame.maxY + 10.0 { - finalStatusFrame.origin.y = max(finalStatusFrame.minY, imageFrame.maxY + 2.0) - if finalStatusFrame.height == 0.0 { - finalStatusFrame.origin.y += 14.0 - - adjustedBoundingSize.height += 14.0 - } - } - } - statusFrame = finalStatusFrame - } - - return (adjustedBoundingSize, { [weak self] animation, synchronousLoads, applyInfo in - if let strongSelf = self { - strongSelf.context = context - strongSelf.message = message - strongSelf.media = mediaAndFlags?.0 - strongSelf.theme = presentationData.theme - - let backgroundView: UIImageView - if let current = strongSelf.backgroundView { - backgroundView = current - } else { - backgroundView = UIImageView() - strongSelf.backgroundView = backgroundView - strongSelf.view.insertSubview(backgroundView, at: 0) - } - - if backgroundView.image == nil { - backgroundView.image = PresentationResourcesChat.chatReplyBackgroundTemplateImage(presentationData.theme.theme) - } - backgroundView.tintColor = mainColor - - animation.animator.updateFrame(layer: backgroundView.layer, frame: CGRect(origin: CGPoint(x: 11.0, y: insets.top - 3.0), size: CGSize(width: adjustedBoundingSize.width - 4.0 - insets.right, height: adjustedBoundingSize.height - insets.top - insets.bottom + 4.0)), completion: nil) - backgroundView.isHidden = !displayLine - - //strongSelf.borderColor = UIColor.red.cgColor - //strongSelf.borderWidth = 2.0 - - strongSelf.textNode.textNode.displaysAsynchronously = !isPreview - - let _ = topTitleApply() - strongSelf.topTitleNode.frame = CGRect(origin: CGPoint(x: textFrame.minX, y: insets.top), size: topTitleLayout.size) - - let _ = textApply(TextNodeWithEntities.Arguments( - context: context, - cache: animationCache, - renderer: animationRenderer, - placeholderColor: messageTheme.mediaPlaceholderColor, - attemptSynchronous: synchronousLoads - )) - switch strongSelf.visibility { - case .none: - strongSelf.textNode.visibilityRect = nil - case let .visible(_, subRect): - var subRect = subRect - subRect.origin.x = 0.0 - subRect.size.width = 10000.0 - strongSelf.textNode.visibilityRect = subRect - } - - if let imageFrame = imageFrame { - if let updateImageSignal = updateInlineImageSignal { - strongSelf.inlineImageNode.setSignal(updateImageSignal) - } - animation.animator.updateFrame(layer: strongSelf.inlineImageNode.layer, frame: imageFrame, completion: nil) - if strongSelf.inlineImageNode.supernode == nil { - strongSelf.addSubnode(strongSelf.inlineImageNode) - } - - if let imageApply = imageApply { - imageApply() - } - } else if strongSelf.inlineImageNode.supernode != nil { - strongSelf.inlineImageNode.removeFromSupernode() - } - - if let (contentImageSize, contentImageApply) = contentImageSizeAndApply { - let contentImageNode = contentImageApply(animation, synchronousLoads) - if strongSelf.contentImageNode !== contentImageNode { - strongSelf.contentImageNode = contentImageNode - contentImageNode.activatePinch = { sourceNode in - controllerInteraction.activateMessagePinch(sourceNode) - } - strongSelf.addSubnode(contentImageNode) - contentImageNode.activateLocalContent = { [weak strongSelf] mode in - if let strongSelf = strongSelf { - strongSelf.openMedia?(mode) - } - } - contentImageNode.updateMessageReaction = { [weak controllerInteraction] message, value in - guard let controllerInteraction = controllerInteraction else { - return - } - controllerInteraction.updateMessageReaction(message, value) - } - contentImageNode.visibility = strongSelf.visibility != .none - } - let _ = contentImageApply(animation, synchronousLoads) - var contentImageFrame: CGRect - if let (_, flags) = mediaAndFlags, flags.contains(.preferMediaBeforeText) { - contentImageFrame = CGRect(origin: CGPoint(x: insets.left, y: insets.top), size: contentImageSize) - if titleBeforeMedia { - contentImageFrame.origin.y += topTitleLayout.size.height + 4.0 - } - } else { - contentImageFrame = CGRect(origin: CGPoint(x: insets.left, y: textFrame.maxY + (textFrame.size.height > CGFloat.ulpOfOne ? 4.0 : 0.0)), size: contentImageSize) - } - - contentImageNode.frame = contentImageFrame - } else if let contentImageNode = strongSelf.contentImageNode { - contentImageNode.visibility = false - contentImageNode.removeFromSupernode() - strongSelf.contentImageNode = nil - } - - if let updatedAdditionalImageBadge = updatedAdditionalImageBadge, let contentImageNode = strongSelf.contentImageNode, let contentImageSize = contentImageSizeAndApply?.0 { - if strongSelf.additionalImageBadgeNode != updatedAdditionalImageBadge { - strongSelf.additionalImageBadgeNode?.removeFromSupernode() - } - strongSelf.additionalImageBadgeNode = updatedAdditionalImageBadge - contentImageNode.addSubnode(updatedAdditionalImageBadge) - if mediaBadge != nil { - updatedAdditionalImageBadge.update(theme: presentationData.theme.theme, content: additionalImageBadgeContent, mediaDownloadState: nil, animated: false) - updatedAdditionalImageBadge.frame = CGRect(origin: CGPoint(x: 2.0, y: 2.0), size: CGSize(width: 0.0, height: 0.0)) - } else { - updatedAdditionalImageBadge.update(theme: presentationData.theme.theme, content: additionalImageBadgeContent, mediaDownloadState: nil, alignment: .right, animated: false) - updatedAdditionalImageBadge.frame = CGRect(origin: CGPoint(x: contentImageSize.width - 6.0, y: contentImageSize.height - 18.0 - 6.0), size: CGSize(width: 0.0, height: 0.0)) - } - } else if let additionalImageBadgeNode = strongSelf.additionalImageBadgeNode { - strongSelf.additionalImageBadgeNode = nil - additionalImageBadgeNode.removeFromSupernode() - } - - if let (contentFileSize, contentFileApply) = contentFileSizeAndApply { - let contentFileNode = contentFileApply(synchronousLoads, animation, applyInfo) - if strongSelf.contentFileNode !== contentFileNode { - strongSelf.contentFileNode = contentFileNode - strongSelf.addSubnode(contentFileNode) - contentFileNode.activateLocalContent = { [weak strongSelf] in - if let strongSelf = strongSelf { - strongSelf.openMedia?(.default) - } - } - contentFileNode.requestUpdateLayout = { [weak strongSelf] _ in - if let strongSelf = strongSelf { - strongSelf.requestUpdateLayout?() - } - } - } - if let (_, flags) = mediaAndFlags, flags.contains(.preferMediaBeforeText) { - contentFileNode.frame = CGRect(origin: CGPoint(x: insets.left, y: insets.top), size: contentFileSize) - } else { - contentFileNode.frame = CGRect(origin: CGPoint(x: insets.left, y: textFrame.maxY + (textFrame.size.height > CGFloat.ulpOfOne ? 8.0 : 7.0)), size: contentFileSize) - } - } else if let contentFileNode = strongSelf.contentFileNode { - contentFileNode.removeFromSupernode() - strongSelf.contentFileNode = nil - } - - if let (videoLayout, apply) = contentInstantVideoSizeAndApply { - let contentInstantVideoNode = apply(.unconstrained(width: boundingWidth - insets.left - insets.right), animation) - if strongSelf.contentInstantVideoNode !== contentInstantVideoNode { - strongSelf.contentInstantVideoNode = contentInstantVideoNode - strongSelf.addSubnode(contentInstantVideoNode) - } - if let (_, flags) = mediaAndFlags, flags.contains(.preferMediaBeforeText) { - contentInstantVideoNode.frame = CGRect(origin: CGPoint(x: insets.left, y: insets.top), size: videoLayout.contentSize) - } else { - contentInstantVideoNode.frame = CGRect(origin: CGPoint(x: insets.left, y: textFrame.maxY + (textFrame.size.height > CGFloat.ulpOfOne ? 4.0 : 0.0)), size: videoLayout.contentSize) - } - } else if let contentInstantVideoNode = strongSelf.contentInstantVideoNode { - contentInstantVideoNode.removeFromSupernode() - strongSelf.contentInstantVideoNode = nil - } - - strongSelf.textNode.textNode.frame = adjustedTextFrame - if let statusSizeAndApply = statusSizeAndApply, let statusFrame = statusFrame { - if strongSelf.statusNode.supernode == nil { - strongSelf.addSubnode(strongSelf.statusNode) - strongSelf.statusNode.frame = statusFrame - statusSizeAndApply.1(.None) - } else { - animation.animator.updateFrame(layer: strongSelf.statusNode.layer, frame: statusFrame, completion: nil) - statusSizeAndApply.1(animation) - } - } else if strongSelf.statusNode.supernode != nil { - strongSelf.statusNode.removeFromSupernode() - } - - if let (size, apply) = actionButtonSizeAndApply { - let buttonNode = apply() - - let buttonFrame = CGRect(origin: CGPoint(x: 12.0, y: adjustedBoundingSize.height - insets.bottom - size.height), size: size) - if buttonNode !== strongSelf.buttonNode { - strongSelf.buttonNode?.removeFromSupernode() - strongSelf.buttonNode = buttonNode - buttonNode.isUserInteractionEnabled = false - strongSelf.addSubnode(buttonNode) - buttonNode.pressed = { - if let strongSelf = self { - strongSelf.activateAction?() - } - } - buttonNode.frame = buttonFrame - } else { - animation.animator.updateFrame(layer: buttonNode.layer, frame: buttonFrame, completion: nil) - } - - let buttonSeparatorFrame = CGRect(origin: CGPoint(x: buttonFrame.minX + 8.0, y: buttonFrame.minY - 2.0), size: CGSize(width: buttonFrame.width - 8.0 - 8.0, height: UIScreenPixel)) - - let buttonSeparatorLayer: SimpleLayer - if let current = strongSelf.buttonSeparatorLayer { - buttonSeparatorLayer = current - animation.animator.updateFrame(layer: buttonSeparatorLayer, frame: buttonSeparatorFrame, completion: nil) - } else { - buttonSeparatorLayer = SimpleLayer() - strongSelf.buttonSeparatorLayer = buttonSeparatorLayer - strongSelf.layer.addSublayer(buttonSeparatorLayer) - buttonSeparatorLayer.frame = buttonSeparatorFrame - } - - buttonSeparatorLayer.backgroundColor = mainColor.withMultipliedAlpha(0.5).cgColor - } else { - if let buttonNode = strongSelf.buttonNode { - strongSelf.buttonNode = nil - buttonNode.removeFromSupernode() - } - - if let buttonSeparatorLayer = strongSelf.buttonSeparatorLayer { - strongSelf.buttonSeparatorLayer = nil - buttonSeparatorLayer.removeFromSuperlayer() - } - } - } - }) - }) - })*/ } } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift index 035efd78f45..2df8623561d 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift @@ -558,9 +558,17 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI private var swipeToReplyFeedback: HapticFeedback? private var nameNode: TextNode? + private var nameButtonNode: HighlightTrackingButtonNode? + private var nameHighlightNode: ASImageNode? + private var viaMeasureNode: TextNode? + private var adminBadgeNode: TextNode? private var credibilityIconView: ComponentHostView? private var credibilityIconComponent: EmojiStatusComponent? + private var credibilityIconContent: EmojiStatusComponent.Content? + private var credibilityButtonNode: HighlightTrackingButtonNode? + private var credibilityHighlightNode: ASImageNode? + private var closeButtonNode: HighlightTrackingButtonNode? private var closeIconNode: ASImageNode? @@ -1094,7 +1102,15 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI return .fail } } - + + if let nameButtonNode = strongSelf.nameButtonNode, nameButtonNode.frame.contains(point) { + return .fail + } + + if let credibilityButtonNode = strongSelf.credibilityButtonNode, credibilityButtonNode.frame.contains(point) { + return .fail + } + if let nameNode = strongSelf.nameNode, nameNode.frame.contains(point) { if let item = strongSelf.item { for attribute in item.message.attributes { @@ -1257,6 +1273,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI } let authorNameLayout = TextNode.asyncLayout(self.nameNode) + let viaMeasureLayout = TextNode.asyncLayout(self.viaMeasureNode) let adminBadgeLayout = TextNode.asyncLayout(self.adminBadgeNode) let threadInfoLayout = ChatMessageThreadInfoNode.asyncLayout(self.threadInfoNode) let forwardInfoLayout = ChatMessageForwardInfoNode.asyncLayout(self.forwardInfoNode) @@ -1280,6 +1297,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI return ChatMessageBubbleItemNode.beginLayout(selfReference: weakSelf, item, params, mergedTop, mergedBottom, dateHeaderAtBottom, currentContentClassesPropertiesAndLayouts: currentContentClassesPropertiesAndLayouts, authorNameLayout: authorNameLayout, + viaMeasureLayout: viaMeasureLayout, adminBadgeLayout: adminBadgeLayout, threadInfoLayout: threadInfoLayout, forwardInfoLayout: forwardInfoLayout, @@ -1299,6 +1317,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI 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), adminBadgeLayout: (TextNodeLayoutArguments) -> (TextNodeLayout, () -> TextNode), threadInfoLayout: (ChatMessageThreadInfoNode.Arguments) -> (CGSize, (Bool) -> ChatMessageThreadInfoNode), forwardInfoLayout: (AccountContext, ChatPresentationData, PresentationStrings, ChatMessageForwardInfoType, Peer?, String?, String?, ChatMessageForwardInfoNode.StoryData?, CGSize) -> (CGSize, (CGFloat) -> ChatMessageForwardInfoNode), @@ -1620,6 +1639,10 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI var hasInstantVideo = false for contentNodeItemValue in contentNodeMessagesAndClasses { let contentNodeItem = contentNodeItemValue as (message: Message, type: AnyClass, attributes: ChatMessageEntryAttributes, bubbleAttributes: BubbleItemAttributes) + if contentNodeItem.type == ChatMessageGiveawayBubbleContentNode.self { + maximumContentWidth = min(305.0, maximumContentWidth) + break + } if contentNodeItem.type == ChatMessageInstantVideoBubbleContentNode.self, !contentNodeItem.bubbleAttributes.isAttachment { maximumContentWidth = baseWidth - 20.0 hasInstantVideo = true @@ -2158,6 +2181,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI var nameNodeOriginY: CGFloat = 0.0 var nameNodeSizeApply: (CGSize, () -> TextNode?) = (CGSize(), { nil }) var adminNodeSizeApply: (CGSize, () -> TextNode?) = (CGSize(), { nil }) + var viaWidth: CGFloat = 0.0 var threadInfoOriginY: CGFloat = 0.0 var threadInfoSizeApply: (CGSize, (Bool) -> ChatMessageThreadInfoNode?) = (CGSize(), { _ in nil }) @@ -2196,6 +2220,8 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI } else if authorIsChannel, case .peer = item.chatLocation { adminBadgeString = NSAttributedString(string: " \(item.presentationData.strings.Channel_Status)", font: inlineBotPrefixFont, textColor: messageTheme.secondaryTextColor) } + + var viaSuffix: NSAttributedString? if let authorNameString = authorNameString, let authorNameColor = authorNameColor, let inlineBotNameString = inlineBotNameString { let mutableString = NSMutableAttributedString(string: "\(authorNameString) ", attributes: [NSAttributedString.Key.font: nameFont, NSAttributedString.Key.foregroundColor: authorNameColor]) let bodyAttributes = MarkdownAttributeSet(font: nameFont, textColor: inlineBotNameColor) @@ -2203,6 +2229,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI let botString = addAttributesToStringWithRanges(item.presentationData.strings.Conversation_MessageViaUser("@\(inlineBotNameString)")._tuple, body: bodyAttributes, argumentAttributes: [0: boldAttributes]) mutableString.append(botString) attributedString = mutableString + viaSuffix = botString } else if let authorNameString = authorNameString, let authorNameColor = authorNameColor { attributedString = NSAttributedString(string: authorNameString, font: nameFont, textColor: authorNameColor) } else if let inlineBotNameString = inlineBotNameString { @@ -2237,6 +2264,11 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI return sizeAndApply.1() }) + if let viaSuffix { + let (viaLayout, _) = viaMeasureLayout(TextNodeLayoutArguments(attributedString: viaSuffix, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: max(0, maximumNodeWidth - layoutConstants.text.bubbleInsets.left - layoutConstants.text.bubbleInsets.right - credibilityIconWidth - adminBadgeSizeAndApply.0.size.width - closeButtonWidth), height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + viaWidth = viaLayout.size.width + 3.0 + } + nameNodeOriginY = headerSize.height headerSize.width = max(headerSize.width, nameNodeSizeApply.0.width + adminBadgeSizeAndApply.0.size.width + credibilityIconWidth + closeButtonWidth + bubbleWidthInsets) headerSize.height += nameNodeSizeApply.0.height @@ -2822,6 +2854,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI backgroundFrame: backgroundFrame, deliveryFailedInset: deliveryFailedInset, nameNodeSizeApply: nameNodeSizeApply, + viaWidth: viaWidth, contentOrigin: contentOrigin, nameNodeOriginY: nameNodeOriginY, authorNameColor: authorNameColor, @@ -2875,6 +2908,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI backgroundFrame: CGRect, deliveryFailedInset: CGFloat, nameNodeSizeApply: (CGSize, () -> TextNode?), + viaWidth: CGFloat, contentOrigin: CGPoint, nameNodeOriginY: CGFloat, authorNameColor: UIColor?, @@ -2907,6 +2941,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI return } + 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) strongSelf.mainContextSourceNode.frame = CGRect(origin: CGPoint(), size: layout.contentSize) @@ -3021,6 +3056,46 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI animation.animator.updateFrame(layer: nameNode.layer, frame: nameNodeFrame, completion: nil) } + let nameButtonNode: HighlightTrackingButtonNode + let nameHighlightNode: ASImageNode + if let currentButton = strongSelf.nameButtonNode, let currentHighlight = strongSelf.nameHighlightNode { + nameButtonNode = currentButton + nameHighlightNode = currentHighlight + } else { + nameHighlightNode = ASImageNode() + nameHighlightNode.alpha = 0.0 + nameHighlightNode.displaysAsynchronously = false + nameHighlightNode.isUserInteractionEnabled = false + strongSelf.clippingNode.addSubnode(nameHighlightNode) + strongSelf.nameHighlightNode = nameHighlightNode + + nameButtonNode = HighlightTrackingButtonNode() + nameButtonNode.highligthedChanged = { [weak nameHighlightNode] highlighted in + guard let nameHighlightNode else { + return + } + if highlighted { + nameHighlightNode.layer.removeAnimation(forKey: "opacity") + nameHighlightNode.alpha = 1.0 + } else { + nameHighlightNode.alpha = 0.0 + nameHighlightNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2) + } + } + nameButtonNode.addTarget(strongSelf, action: #selector(strongSelf.nameButtonPressed), forControlEvents: .touchUpInside) + strongSelf.clippingNode.addSubnode(nameButtonNode) + strongSelf.nameButtonNode = nameButtonNode + } + var nameHiglightFrame = nameNodeFrame + nameHiglightFrame.size.width -= viaWidth + nameHighlightNode.frame = nameHiglightFrame.insetBy(dx: -2.0, dy: -1.0) + nameButtonNode.frame = nameHiglightFrame.insetBy(dx: -2.0, dy: -3.0) + + let nameColor = authorNameColor ?? item.presentationData.theme.theme.chat.message.outgoing.accentTextColor + if themeUpdated { + nameHighlightNode.image = generateFilledRoundedRectImage(size: CGSize(width: 8.0, height: 8.0), cornerRadius: 4.0, color: nameColor.withAlphaComponent(0.1))?.stretchableImage(withLeftCapWidth: 4, topCapHeight: 4) + } + if let currentCredibilityIcon = currentCredibilityIcon { let credibilityIconView: ComponentHostView if let current = strongSelf.credibilityIconView { @@ -3045,6 +3120,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI action: nil ) strongSelf.credibilityIconComponent = credibilityIconComponent + strongSelf.credibilityIconContent = currentCredibilityIcon let credibilityIconSize = credibilityIconView.update( transition: .immediate, @@ -3053,10 +3129,49 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI containerSize: CGSize(width: 20.0, height: 20.0) ) - credibilityIconView.frame = CGRect(origin: CGPoint(x: nameNode.frame.maxX + 3.0, y: nameNode.frame.minY + floor((nameNode.bounds.height - credibilityIconSize.height) / 2.0)), size: credibilityIconSize) + let credibilityIconFrame = CGRect(origin: CGPoint(x: nameNode.frame.maxX + 3.0, y: nameNode.frame.minY + floor((nameNode.bounds.height - credibilityIconSize.height) / 2.0)), size: credibilityIconSize) + credibilityIconView.frame = credibilityIconFrame + + let credibilityButtonNode: HighlightTrackingButtonNode + let credibilityHighlightNode: ASImageNode + if let currentButton = strongSelf.credibilityButtonNode, let currentHighlight = strongSelf.credibilityHighlightNode { + credibilityButtonNode = currentButton + credibilityHighlightNode = currentHighlight + } else { + credibilityHighlightNode = ASImageNode() + credibilityHighlightNode.alpha = 0.0 + credibilityHighlightNode.displaysAsynchronously = false + credibilityHighlightNode.isUserInteractionEnabled = false + strongSelf.clippingNode.addSubnode(credibilityHighlightNode) + strongSelf.credibilityHighlightNode = credibilityHighlightNode + + credibilityButtonNode = HighlightTrackingButtonNode() + credibilityButtonNode.highligthedChanged = { [weak credibilityHighlightNode] highlighted in + guard let credibilityHighlightNode else { + return + } + if highlighted { + credibilityHighlightNode.layer.removeAnimation(forKey: "opacity") + credibilityHighlightNode.alpha = 1.0 + } else { + credibilityHighlightNode.alpha = 0.0 + credibilityHighlightNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2) + } + } + credibilityButtonNode.addTarget(strongSelf, action: #selector(strongSelf.credibilityButtonPressed), forControlEvents: .touchUpInside) + strongSelf.clippingNode.addSubnode(credibilityButtonNode) + strongSelf.credibilityButtonNode = credibilityButtonNode + } + credibilityHighlightNode.frame = credibilityIconFrame.insetBy(dx: -1.0, dy: -1.0) + credibilityButtonNode.frame = credibilityIconFrame.insetBy(dx: -2.0, dy: -3.0) + + if themeUpdated { + credibilityHighlightNode.image = generateFilledRoundedRectImage(size: CGSize(width: 8.0, height: 8.0), cornerRadius: 4.0, color: nameColor.withAlphaComponent(0.1))?.stretchableImage(withLeftCapWidth: 4, topCapHeight: 4) + } } else { strongSelf.credibilityIconView?.removeFromSuperview() strongSelf.credibilityIconView = nil + strongSelf.credibilityIconContent = nil } if let adminBadgeNode = adminNodeSizeApply.1() { @@ -3156,6 +3271,14 @@ 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 } } @@ -4656,6 +4779,14 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI return result } + if let nameButtonNode = self.nameButtonNode, nameButtonNode.frame.contains(point) { + return nameButtonNode.view + } + + if let credibilityButtonNode = self.credibilityButtonNode, credibilityButtonNode.frame.contains(point) { + return credibilityButtonNode.view + } + if let shareButtonNode = self.shareButtonNode, shareButtonNode.frame.contains(point) { return shareButtonNode.view } @@ -5118,6 +5249,28 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI } } + @objc private func nameButtonPressed() { + if let item = self.item, let peer = item.message.author { + let messageReference = MessageReference(item.message) + if let channel = peer as? TelegramChannel, case .broadcast = channel.info { + item.controllerInteraction.openPeer(EnginePeer(peer), .chat(textInputState: nil, subject: nil, peekData: nil), messageReference, .default) + } else { + item.controllerInteraction.openPeer(EnginePeer(peer), .info, messageReference, .groupParticipant(storyStats: nil, avatarHeaderNode: nil)) + } + } + } + + @objc private func credibilityButtonPressed() { + if let item = self.item, let credibilityIconView = self.credibilityIconView, let iconContent = self.credibilityIconContent, let peer = item.message.author { + var emojiFileId: Int64? + if case let .animation(content, _, _, _, _) = iconContent { + emojiFileId = content.fileId.id + } + + item.controllerInteraction.openPremiumStatusInfo(peer.id, credibilityIconView, emojiFileId, peer.nameColor ?? .blue) + } + } + private var playedSwipeToReplyHaptic = false @objc private func swipeToReplyGesture(_ recognizer: ChatSwipeToReplyRecognizer) { var offset: CGFloat = 0.0 diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/Sources/ChatMessageGiftBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/Sources/ChatMessageGiftBubbleContentNode.swift index ea1f605bf79..435105c17aa 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/Sources/ChatMessageGiftBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/Sources/ChatMessageGiftBubbleContentNode.swift @@ -28,7 +28,6 @@ private func attributedServiceMessageString(theme: ChatPresentationThemeData, st public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { private let labelNode: TextNode private var backgroundNode: WallpaperBubbleBackgroundNode? - private var backgroundColorNode: ASDisplayNode private let backgroundMaskNode: ASImageNode private var linkHighlightingNode: LinkHighlightingNode? @@ -78,7 +77,6 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { self.labelNode.isUserInteractionEnabled = false self.labelNode.displaysAsynchronously = false - self.backgroundColorNode = ASDisplayNode() self.backgroundMaskNode = ASImageNode() self.mediaBackgroundNode = NavigationBackgroundNode(color: .clear) @@ -367,9 +365,7 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { strongSelf.updateVisibility() strongSelf.labelNode.isHidden = !hasServiceMessage - - strongSelf.backgroundColorNode.backgroundColor = selectDateFillStaticColor(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper) - + let imageFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((backgroundSize.width - giftSize.width) / 2.0), y: hasServiceMessage ? labelLayout.size.height + 16.0 : 0.0), size: giftSize) let mediaBackgroundFrame = imageFrame.insetBy(dx: -2.0, dy: -2.0) strongSelf.mediaBackgroundNode.frame = mediaBackgroundFrame @@ -426,7 +422,6 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { if strongSelf.backgroundNode == nil { if let backgroundNode = item.controllerInteraction.presentationContext.backgroundNode?.makeBubbleBackground(for: .free) { strongSelf.backgroundNode = backgroundNode - backgroundNode.addSubnode(strongSelf.backgroundColorNode) strongSelf.insertSubnode(backgroundNode, at: 0) } } @@ -448,7 +443,6 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { } strongSelf.backgroundMaskNode.image = image strongSelf.backgroundMaskNode.frame = CGRect(origin: CGPoint(), size: image.size) - strongSelf.backgroundColorNode.frame = CGRect(origin: CGPoint(), size: image.size) strongSelf.cachedMaskBackgroundImage = (offset, image, labelRects) } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageGiveawayBubbleContentNode/Sources/ChatMessageGiveawayBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageGiveawayBubbleContentNode/Sources/ChatMessageGiveawayBubbleContentNode.swift index 9679cac715a..d65462b819c 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageGiveawayBubbleContentNode/Sources/ChatMessageGiveawayBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageGiveawayBubbleContentNode/Sources/ChatMessageGiveawayBubbleContentNode.swift @@ -218,13 +218,17 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode let backgroundColor = incoming ? item.presentationData.theme.theme.chat.message.incoming.bubble.withoutWallpaper.fill.first! : item.presentationData.theme.theme.chat.message.outgoing.bubble.withoutWallpaper.fill.first! let textColor = incoming ? item.presentationData.theme.theme.chat.message.incoming.primaryTextColor : item.presentationData.theme.theme.chat.message.outgoing.primaryTextColor let accentColor = incoming ? item.presentationData.theme.theme.chat.message.incoming.accentTextColor : item.presentationData.theme.theme.chat.message.outgoing.accentTextColor + var badgeTextColor: UIColor = .white + if badgeTextColor.distance(to: accentColor) < 1 { + badgeTextColor = incoming ? item.presentationData.theme.theme.chat.message.incoming.bubble.withoutWallpaper.fill.first! : item.presentationData.theme.theme.chat.message.outgoing.bubble.withoutWallpaper.fill.first! + } var updatedBadgeImage: UIImage? if themeUpdated { updatedBadgeImage = generateStretchableFilledCircleImage(diameter: 21.0, color: accentColor, strokeColor: backgroundColor, strokeWidth: 1.0 + UIScreenPixel, backgroundColor: nil) } - let badgeString = NSAttributedString(string: "X\(giveaway?.quantity ?? 1)", font: Font.with(size: 10.0, design: .round , weight: .bold, traits: .monospacedNumbers), textColor: .white) + let badgeString = NSAttributedString(string: "X\(giveaway?.quantity ?? 1)", font: Font.with(size: 10.0, design: .round , weight: .bold, traits: .monospacedNumbers), textColor: badgeTextColor) let prizeTitleString = NSAttributedString(string: item.presentationData.strings.Chat_Giveaway_Message_PrizeTitle, font: titleFont, textColor: textColor) var prizeTextString: NSAttributedString? @@ -315,7 +319,7 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode let (participantsTitleLayout, participantsTitleApply) = makeParticipantsTitleLayout(TextNodeLayoutArguments(attributedString: participantsTitleString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: maxTextWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets())) let (participantsTextLayout, participantsTextApply) = makeParticipantsTextLayout(TextNodeLayoutArguments(attributedString: participantsTextString, backgroundColor: nil, maximumNumberOfLines: 5, truncationType: .end, constrainedSize: CGSize(width: maxTextWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets())) - let (countriesTextLayout, countriesTextApply) = makeCountriesTextLayout(TextNodeLayoutArguments(attributedString: countriesTextString, backgroundColor: nil, maximumNumberOfLines: 5, truncationType: .end, constrainedSize: CGSize(width: maxTextWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets())) + let (countriesTextLayout, countriesTextApply) = makeCountriesTextLayout(TextNodeLayoutArguments(attributedString: countriesTextString, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: maxTextWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets())) let (dateTitleLayout, dateTitleApply) = makeDateTitleLayout(TextNodeLayoutArguments(attributedString: dateTitleString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: maxTextWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets())) let (dateTextLayout, dateTextApply) = makeDateTextLayout(TextNodeLayoutArguments(attributedString: dateTextString, backgroundColor: nil, maximumNumberOfLines: 5, truncationType: .end, constrainedSize: CGSize(width: maxTextWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets())) @@ -433,7 +437,7 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode } } } - let (channelsWidth, continueChannelLayout) = makeChannelsLayout(item.context, 240.0, channelPeers, accentColor, accentColor.withAlphaComponent(0.1)) + let (channelsWidth, continueChannelLayout) = makeChannelsLayout(item.context, 220.0, channelPeers, accentColor, accentColor.withAlphaComponent(0.1), incoming, item.presentationData.theme.theme.overallDarkAppearance) maxContentWidth = max(maxContentWidth, channelsWidth) maxContentWidth += 30.0 @@ -645,11 +649,11 @@ private final class PeerButtonsStackNode: ASDisplayNode { var buttonNodes: [PeerButtonNode] = [] var openPeer: (EnginePeer) -> Void = { _ in } - static func asyncLayout(_ current: PeerButtonsStackNode) -> (_ context: AccountContext, _ width: CGFloat, _ peers: [EnginePeer], _ titleColor: UIColor, _ backgroundColor: UIColor) -> (CGFloat, (CGFloat) -> (CGSize, () -> PeerButtonsStackNode)) { + static func asyncLayout(_ current: PeerButtonsStackNode) -> (_ context: AccountContext, _ width: CGFloat, _ peers: [EnginePeer], _ titleColor: UIColor, _ backgroundColor: UIColor, _ incoming: Bool, _ dark: Bool) -> (CGFloat, (CGFloat) -> (CGSize, () -> PeerButtonsStackNode)) { let currentChannelButtons = current.buttonNodes.isEmpty ? nil : current.buttonNodes let maybeMakeChannelButtons = current.buttonNodes.map(PeerButtonNode.asyncLayout) - return { context, width, peers, titleColor, backgroundColor in + return { context, width, peers, titleColor, backgroundColor, incoming, dark in let targetNode = current var buttonNodes: [PeerButtonNode] = [] @@ -678,6 +682,13 @@ private final class PeerButtonsStackNode: ASDisplayNode { let peer = peers[i] let makeChannelButtonLayout = makeChannelButtonLayouts[i] + var titleColor = titleColor + var backgroundColor = backgroundColor + if incoming, let nameColor = peer.nameColor { + titleColor = context.peerNameColors.get(nameColor, dark: dark).main + backgroundColor = titleColor.withAlphaComponent(0.1) + } + let (buttonWidth, buttonContinue) = makeChannelButtonLayout(context, width, peer, titleColor, backgroundColor) sizes.append(CGSize(width: buttonWidth, height: buttonHeight)) buttonContinues.append(buttonContinue) diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveFileNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveFileNode/BUILD index 31c4d55db6d..bfe7061fab3 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveFileNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveFileNode/BUILD @@ -5,7 +5,7 @@ NGDEPS = [ "//Nicegram/NGTelegramIntegration:NGTelegramIntegration", "//Nicegram/NGTranslate:NGTranslate", "//Nicegram/NGUI:NGUI", - "@swiftpkg_nicegram_assistant_ios//:Sources_NGPremiumUI", + "@swiftpkg_nicegram_assistant_ios//:Sources_FeatPremiumUI", ] swift_library( diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveFileNode/Sources/ChatMessageInteractiveFileNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveFileNode/Sources/ChatMessageInteractiveFileNode.swift index bfc4b66314b..e20a180534d 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveFileNode/Sources/ChatMessageInteractiveFileNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveFileNode/Sources/ChatMessageInteractiveFileNode.swift @@ -1,6 +1,6 @@ // MARK: Nicegram Imports +import FeatPremiumUI import NGData -import NGPremiumUI import NGStrings import NGTelegramIntegration import NGTranslate diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveInstantVideoNode/Sources/ChatMessageInteractiveInstantVideoNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveInstantVideoNode/Sources/ChatMessageInteractiveInstantVideoNode.swift index 84e8df304ad..f0aa5172149 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveInstantVideoNode/Sources/ChatMessageInteractiveInstantVideoNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveInstantVideoNode/Sources/ChatMessageInteractiveInstantVideoNode.swift @@ -433,7 +433,7 @@ public class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { forwardAuthorSignature = forwardInfo.authorSignature } } - let availableWidth: CGFloat = max(60.0, availableContentWidth - 210.0 + 6.0) + let availableWidth: CGFloat = max(60.0, availableContentWidth - 220.0 + 6.0) forwardInfoSizeApply = makeForwardInfoLayout(item.context, item.presentationData, item.presentationData.strings, .standalone, forwardSource, forwardAuthorSignature, forwardPsaType, nil, CGSize(width: availableWidth, height: CGFloat.greatestFiniteMagnitude)) } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode/Sources/ChatMessageWebpageBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode/Sources/ChatMessageWebpageBubbleContentNode.swift index 17e3d8a0e7b..6634fce5e59 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode/Sources/ChatMessageWebpageBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode/Sources/ChatMessageWebpageBubbleContentNode.swift @@ -22,6 +22,31 @@ import ChatControllerInteraction private let titleFont: UIFont = Font.semibold(15.0) +public func defaultWebpageImageSizeIsSmall(webpage: TelegramMediaWebpageLoadedContent) -> Bool { + let type = websiteType(of: webpage.websiteName) + + let mainMedia: Media? + switch type { + case .instagram, .twitter: + mainMedia = webpage.story ?? webpage.image ?? webpage.file + default: + mainMedia = webpage.story ?? webpage.file ?? webpage.image + } + + if let image = mainMedia as? TelegramMediaImage { + if let type = webpage.type, (["photo", "video", "embed", "gif", "document", "telegram_album"] as [String]).contains(type) { + } else if let type = webpage.type, (["article"] as [String]).contains(type) { + return true + } else if let _ = largestImageRepresentation(image.representations)?.dimensions { + if webpage.instantPage == nil { + return true + } + } + } + + return false +} + public final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode { private var webPage: TelegramMediaWebpage? @@ -309,10 +334,7 @@ public final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContent } mediaAndFlags = (image, flags) } else if let _ = largestImageRepresentation(image.representations)?.dimensions { - var flags = ChatMessageAttachedContentNodeMediaFlags() - if webpage.instantPage == nil { - flags.insert(.preferMediaInline) - } + let flags = ChatMessageAttachedContentNodeMediaFlags() mediaAndFlags = (image, flags) } } else if let story = mainMedia as? TelegramMediaStory { @@ -441,6 +463,10 @@ public final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContent } } + if defaultWebpageImageSizeIsSmall(webpage: webpage) { + mediaAndFlags?.1.insert(.preferMediaInline) + } + if let webPageContent, let isMediaLargeByDefault = webPageContent.isMediaLargeByDefault, !isMediaLargeByDefault { mediaAndFlags?.1.insert(.preferMediaInline) } else if let attribute = item.message.attributes.first(where: { $0 is WebpagePreviewMessageAttribute }) as? WebpagePreviewMessageAttribute { diff --git a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift index 047d39e37da..ea1badefeae 100644 --- a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift @@ -569,6 +569,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { }, saveMediaToFiles: { _ in }, openNoAdsDemo: { }, displayGiveawayParticipationStatus: { _ in + }, openPremiumStatusInfo: { _, _, _, _ in }, requestMessageUpdate: { _, _ in }, cancelInteractiveKeyboardGestures: { }, dismissTextInput: { diff --git a/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift b/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift index 69360a1cd1c..08f91020340 100644 --- a/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift +++ b/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift @@ -213,6 +213,7 @@ public final class ChatControllerInteraction { public let saveMediaToFiles: (EngineMessage.Id) -> Void public let openNoAdsDemo: () -> Void public let displayGiveawayParticipationStatus: (EngineMessage.Id) -> Void + public let openPremiumStatusInfo: (EnginePeer.Id, UIView, Int64?, PeerNameColor) -> Void public let requestMessageUpdate: (MessageId, Bool) -> Void public let cancelInteractiveKeyboardGestures: () -> Void @@ -334,6 +335,7 @@ public final class ChatControllerInteraction { saveMediaToFiles: @escaping (EngineMessage.Id) -> Void, openNoAdsDemo: @escaping () -> Void, displayGiveawayParticipationStatus: @escaping (EngineMessage.Id) -> Void, + openPremiumStatusInfo: @escaping (EnginePeer.Id, UIView, Int64?, PeerNameColor) -> Void, requestMessageUpdate: @escaping (MessageId, Bool) -> Void, cancelInteractiveKeyboardGestures: @escaping () -> Void, dismissTextInput: @escaping () -> Void, @@ -437,6 +439,7 @@ public final class ChatControllerInteraction { self.saveMediaToFiles = saveMediaToFiles self.openNoAdsDemo = openNoAdsDemo self.displayGiveawayParticipationStatus = displayGiveawayParticipationStatus + self.openPremiumStatusInfo = openPremiumStatusInfo self.requestMessageUpdate = requestMessageUpdate self.cancelInteractiveKeyboardGestures = cancelInteractiveKeyboardGestures self.dismissTextInput = dismissTextInput diff --git a/submodules/TelegramUI/Components/EmojiStatusComponent/Sources/EmojiStatusComponent.swift b/submodules/TelegramUI/Components/EmojiStatusComponent/Sources/EmojiStatusComponent.swift index fdcc76a0ddd..800502be734 100644 --- a/submodules/TelegramUI/Components/EmojiStatusComponent/Sources/EmojiStatusComponent.swift +++ b/submodules/TelegramUI/Components/EmojiStatusComponent/Sources/EmojiStatusComponent.swift @@ -23,7 +23,7 @@ public final class EmojiStatusComponent: Component { case file(file: TelegramMediaFile) case customEmoji(fileId: Int64) - var fileId: MediaId { + public var fileId: MediaId { switch self { case let .file(file): return file.fileId diff --git a/submodules/TelegramUI/Components/Resources/FetchVideoMediaResource/Sources/FetchVideoMediaResource.swift b/submodules/TelegramUI/Components/Resources/FetchVideoMediaResource/Sources/FetchVideoMediaResource.swift index 5acad8a76b9..c766b9e28b6 100644 --- a/submodules/TelegramUI/Components/Resources/FetchVideoMediaResource/Sources/FetchVideoMediaResource.swift +++ b/submodules/TelegramUI/Components/Resources/FetchVideoMediaResource/Sources/FetchVideoMediaResource.swift @@ -886,6 +886,11 @@ private extension MediaEditorValues { if let paintingData = legacyAdjustments.paintingData { if let entitiesData = paintingData.entitiesData { entities = decodeCodableDrawingEntities(data: entitiesData) + + let hasAnimation = entities.first(where: { $0.entity.isAnimated }) != nil + if !hasAnimation { + entities = [] + } } if let imagePath = paintingData.imagePath, let image = UIImage(contentsOfFile: imagePath) { drawing = image diff --git a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/PeerNameColorScreen.swift b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/PeerNameColorScreen.swift index 6a235ce7376..70e291a38e0 100644 --- a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/PeerNameColorScreen.swift +++ b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/PeerNameColorScreen.swift @@ -233,7 +233,7 @@ private func peerNameColorScreenEntries( } let messageItem = PeerNameColorChatPreviewItem.MessageItem( outgoing: false, - peerId: peer.id, + peerId: PeerId(namespace: peer.id.namespace, id: PeerId.Id._internalFromInt64Value(0)), author: peer.compactDisplayTitle, photo: peer.profileImageRepresentations, nameColor: nameColor, diff --git a/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/CountriesMultiselectionScreen.swift b/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/CountriesMultiselectionScreen.swift index a4d93ce24f2..1559896c2fe 100644 --- a/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/CountriesMultiselectionScreen.swift +++ b/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/CountriesMultiselectionScreen.swift @@ -468,7 +468,8 @@ final class CountriesMultiselectionScreenComponent: Component { self.hapticFeedback.error() let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } - controller.present(UndoOverlayController(presentationData: presentationData, content: .info(title: nil, text: "You can select maximum \(limit) countries.", timeout: nil, customUndoText: nil), elevatedLayout: false, position: .bottom, animateInAsReplacement: false, action: { _ in return false }), in: .current) + let countriesValue = environment.strings.CountriesList_MaximumReached_Countries(limit) + controller.present(UndoOverlayController(presentationData: presentationData, content: .info(title: nil, text: environment.strings.CountriesList_MaximumReached(countriesValue).string, timeout: nil, customUndoText: nil), elevatedLayout: false, position: .bottom, animateInAsReplacement: false, action: { _ in return false }), in: .current) return } toggleCountry() @@ -721,7 +722,7 @@ final class CountriesMultiselectionScreenComponent: Component { )) } - let placeholder: String = "Search" + let placeholder: String = environment.strings.CountriesList_Search self.navigationTextField.parentState = state let navigationTextFieldSize = self.navigationTextField.update( transition: transition, @@ -829,9 +830,9 @@ final class CountriesMultiselectionScreenComponent: Component { } navigationButtonsWidth += navigationLeftButtonSize.width + navigationSideInset - let actionButtonTitle = "Save Countries" - let title = "Select Countries" - let subtitle = "select up to \(component.context.userLimits.maxGiveawayCountriesCount) countries" + let actionButtonTitle = environment.strings.CountriesList_SaveCountries + let title = environment.strings.CountriesList_SelectCountries + let subtitle = environment.strings.CountriesList_SelectUpTo(component.context.userLimits.maxGiveawayCountriesCount) let titleComponent = AnyComponent( List([ diff --git a/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreenState.swift b/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreenState.swift index 0a2bde11db5..241ee1d82d0 100644 --- a/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreenState.swift +++ b/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreenState.swift @@ -588,17 +588,22 @@ public extension ShareWithPeersScreen { case let .channels(excludePeerIds, searchQuery): self.stateDisposable = (combineLatest( context.engine.messages.chatList(group: .root, count: 500) |> take(1), + searchQuery.flatMap { context.engine.contacts.searchLocalPeers(query: $0) } ?? .single([]), context.engine.data.get(EngineDataMap(Array(self.initialPeerIds).map(TelegramEngine.EngineData.Item.Peer.Peer.init))) ) - |> mapToSignal { chatList, initialPeers -> Signal<(EngineChatList, [EnginePeer.Id: Optional], [EnginePeer.Id: Optional]), NoError> in + |> mapToSignal { chatList, searchResults, initialPeers -> Signal<(EngineChatList, [EngineRenderedPeer], [EnginePeer.Id: Optional], [EnginePeer.Id: Optional]), NoError> in + var peerIds: [EnginePeer.Id] = [] + peerIds.append(contentsOf: chatList.items.map(\.renderedPeer.peerId)) + peerIds.append(contentsOf: searchResults.map(\.peerId)) + peerIds.append(contentsOf: initialPeers.compactMap(\.value?.id)) return context.engine.data.subscribe( EngineDataMap(chatList.items.map(\.renderedPeer.peerId).map(TelegramEngine.EngineData.Item.Peer.ParticipantCount.init)) ) - |> map { participantCountMap -> (EngineChatList, [EnginePeer.Id: Optional], [EnginePeer.Id: Optional]) in - return (chatList, initialPeers, participantCountMap) + |> map { participantCountMap -> (EngineChatList, [EngineRenderedPeer], [EnginePeer.Id: Optional], [EnginePeer.Id: Optional]) in + return (chatList, searchResults, initialPeers, participantCountMap) } } - |> deliverOnMainQueue).start(next: { [weak self] chatList, initialPeers, participantCounts in + |> deliverOnMainQueue).start(next: { [weak self] chatList, searchResults, initialPeers, participantCounts in guard let self else { return } @@ -612,7 +617,7 @@ public extension ShareWithPeersScreen { var existingIds = Set() var selectedPeers: [EnginePeer] = [] - + for item in chatList.items.reversed() { if let peer = item.renderedPeer.peer { if self.initialPeerIds.contains(peer.id) { @@ -628,6 +633,13 @@ public extension ShareWithPeersScreen { existingIds.insert(peerId) } } + + for item in searchResults { + if let peer = item.peer, case let .channel(channel) = peer, case .broadcast = channel.info { + selectedPeers.append(peer) + existingIds.insert(peer.id) + } + } let queryTokens = stringIndexTokens(searchQuery ?? "", transliteration: .combined) func peerMatchesTokens(peer: EnginePeer, tokens: [ValueBoxKey]) -> Bool { @@ -640,9 +652,15 @@ public extension ShareWithPeersScreen { var peers: [EnginePeer] = [] peers = chatList.items.filter { peer in if let peer = peer.renderedPeer.peer { + if existingIds.contains(peer.id) { + return false + } if excludePeerIds.contains(peer.id) { return false } + if peer.isFake || peer.isScam { + return false + } if let _ = searchQuery, !peerMatchesTokens(peer: peer, tokens: queryTokens) { return false } diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContainerScreen.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContainerScreen.swift index 971f2684e84..96c0b8a7a06 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContainerScreen.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContainerScreen.swift @@ -538,7 +538,11 @@ private final class StoryContainerScreenComponent: Component { let fraction = translation.x / (self.bounds.width / 2.0) timestamp = initialSeekTimestamp + duration * fraction } - visibleItemView.seekTo(max(0.0, min(duration, timestamp)), apply: apply) + if translation.y < 64.0 { + visibleItemView.seekTo(max(0.0, min(duration, timestamp)), apply: apply) + } else { + visibleItemView.seekTo(initialSeekTimestamp, apply: apply) + } } longPressRecognizer.updatePanEnded = { [weak self] in guard let self else { diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Info/StatsIcon.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Info/StatsIcon.imageset/Contents.json new file mode 100644 index 00000000000..b7a0fdc9123 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Info/StatsIcon.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "stats_30.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Info/StatsIcon.imageset/stats_30.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Info/StatsIcon.imageset/stats_30.pdf new file mode 100644 index 00000000000..019b3076778 Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Chat/Info/StatsIcon.imageset/stats_30.pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Message/TextCodeIcon.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Message/TextCodeIcon.imageset/Contents.json new file mode 100644 index 00000000000..52f3d73f1dd --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Message/TextCodeIcon.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "codemini.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Message/TextCodeIcon.imageset/codemini.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Message/TextCodeIcon.imageset/codemini.pdf new file mode 100644 index 00000000000..97779fd7ee5 Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Chat/Message/TextCodeIcon.imageset/codemini.pdf differ diff --git a/submodules/TelegramUI/Resources/Animations/message_preview_person_off.json b/submodules/TelegramUI/Resources/Animations/message_preview_person_off.json index 88d5e36d1cd..37bc11c8c38 100644 --- a/submodules/TelegramUI/Resources/Animations/message_preview_person_off.json +++ b/submodules/TelegramUI/Resources/Animations/message_preview_person_off.json @@ -1 +1 @@ -{"v":"5.9.0","fr":60,"ip":0,"op":20,"w":24,"h":24,"nm":"person_off_24","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"cross","parent":4,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,15,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[8,8],[-8,-8]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1.33,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[600,600],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"icon","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[100]},{"t":10,"s":[0]}],"ix":1},"e":{"a":0,"k":100,"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":1200,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"cross mask","parent":4,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-4.501,19.5,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[7.75,7.75],[-7.75,-7.75]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.654166638851,0.654166638851,0.654166638851,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1.33,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[600,600],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"icon","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[100]},{"t":10,"s":[0]}],"ix":1},"e":{"a":0,"k":100,"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":1200,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"icon","parent":4,"tt":2,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.001,-1.5,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,2.024],[-2.024,0],[0,-2.024],[2.024,0]],"o":[[0,-2.024],[2.024,0],[0,2.024],[-2.024,0]],"v":[[-3.665,-4.25],[0,-7.915],[3.665,-4.25],[0,-0.585]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[1.29,0],[0,-1.29],[-1.29,0],[0,1.29]],"o":[[-1.29,0],[0,1.29],[1.29,0],[0,-1.29]],"v":[[0,-6.585],[-2.335,-4.25],[0,-1.915],[2.335,-4.25]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ind":2,"ty":"sh","ix":3,"ks":{"a":0,"k":{"i":[[-1.142,0.771],[-2.089,0],[-1.144,-0.773],[-0.413,-0.921],[0.532,-0.627],[0.869,0],[0,0],[0.516,0.608],[-0.396,0.881]],"o":[[1.144,-0.773],[2.089,0],[1.142,0.771],[0.396,0.881],[-0.516,0.608],[0,0],[-0.869,0],[-0.532,-0.627],[0.413,-0.921]],"v":[[-4.787,1.865],[0,0.585],[4.787,1.865],[7.031,4.561],[6.699,6.944],[4.5,7.915],[-4.5,7.915],[-6.699,6.944],[-7.031,4.561]],"c":true},"ix":2},"nm":"Path 3","mn":"ADBE Vector Shape - Group","hd":false},{"ind":3,"ty":"sh","ix":4,"ks":{"a":0,"k":{"i":[[0.892,-0.602],[0.335,-0.746],[-0.226,-0.267],[-0.511,0],[0,0],[-0.243,0.286],[0.17,0.378],[0.894,0.604],[1.867,0]],"o":[[-0.894,0.604],[-0.17,0.378],[0.243,0.286],[0,0],[0.511,0],[0.226,-0.267],[-0.335,-0.746],[-0.892,-0.602],[-1.867,0]],"v":[[-4.043,2.968],[-5.818,5.105],[-5.685,6.084],[-4.5,6.585],[4.5,6.585],[5.685,6.084],[5.818,5.105],[4.043,2.968],[0,1.915]],"c":true},"ix":2},"nm":"Path 4","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"Merge Paths 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[600,600],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"icon","np":6,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1200,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":3,"nm":"Rectangle 222","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[12,12,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"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":[16.667,16.667,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":10,"s":[15,15,100]},{"t":19,"s":[16.667,16.667,100]}],"ix":6,"l":2}},"ao":0,"ip":0,"op":1200,"st":0,"bm":0}],"markers":[]} \ No newline at end of file +{"v":"5.9.0","fr":60,"ip":0,"op":20,"w":24,"h":24,"nm":"person_on_24","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"cross","parent":4,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,15,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[8,8],[-8,-8]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1.33,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[600,600],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"icon","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[0]},{"t":10,"s":[100]}],"ix":1},"e":{"a":0,"k":100,"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":1200,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"cross mask","parent":4,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-4.501,19.5,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[7.75,7.75],[-7.75,-7.75]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.654166638851,0.654166638851,0.654166638851,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1.33,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[600,600],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"icon","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[0]},{"t":10,"s":[100]}],"ix":1},"e":{"a":0,"k":100,"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":1200,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"icon","parent":4,"tt":2,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.001,-1.5,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,2.024],[-2.024,0],[0,-2.024],[2.024,0]],"o":[[0,-2.024],[2.024,0],[0,2.024],[-2.024,0]],"v":[[-3.665,-4.25],[0,-7.915],[3.665,-4.25],[0,-0.585]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[1.29,0],[0,-1.29],[-1.29,0],[0,1.29]],"o":[[-1.29,0],[0,1.29],[1.29,0],[0,-1.29]],"v":[[0,-6.585],[-2.335,-4.25],[0,-1.915],[2.335,-4.25]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ind":2,"ty":"sh","ix":3,"ks":{"a":0,"k":{"i":[[-1.142,0.771],[-2.089,0],[-1.144,-0.773],[-0.413,-0.921],[0.532,-0.627],[0.869,0],[0,0],[0.516,0.608],[-0.396,0.881]],"o":[[1.144,-0.773],[2.089,0],[1.142,0.771],[0.396,0.881],[-0.516,0.608],[0,0],[-0.869,0],[-0.532,-0.627],[0.413,-0.921]],"v":[[-4.787,1.865],[0,0.585],[4.787,1.865],[7.031,4.561],[6.699,6.944],[4.5,7.915],[-4.5,7.915],[-6.699,6.944],[-7.031,4.561]],"c":true},"ix":2},"nm":"Path 3","mn":"ADBE Vector Shape - Group","hd":false},{"ind":3,"ty":"sh","ix":4,"ks":{"a":0,"k":{"i":[[0.892,-0.602],[0.335,-0.746],[-0.226,-0.267],[-0.511,0],[0,0],[-0.243,0.286],[0.17,0.378],[0.894,0.604],[1.867,0]],"o":[[-0.894,0.604],[-0.17,0.378],[0.243,0.286],[0,0],[0.511,0],[0.226,-0.267],[-0.335,-0.746],[-0.892,-0.602],[-1.867,0]],"v":[[-4.043,2.968],[-5.818,5.105],[-5.685,6.084],[-4.5,6.585],[4.5,6.585],[5.685,6.084],[5.818,5.105],[4.043,2.968],[0,1.915]],"c":true},"ix":2},"nm":"Path 4","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"Merge Paths 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[600,600],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"icon","np":6,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1200,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":3,"nm":"Rectangle 222","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[12,12,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"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":[16.667,16.667,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":10,"s":[15,15,100]},{"t":19,"s":[16.667,16.667,100]}],"ix":6,"l":2}},"ao":0,"ip":0,"op":1200,"st":0,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/submodules/TelegramUI/Resources/Animations/message_preview_person_on.json b/submodules/TelegramUI/Resources/Animations/message_preview_person_on.json index 37bc11c8c38..88d5e36d1cd 100644 --- a/submodules/TelegramUI/Resources/Animations/message_preview_person_on.json +++ b/submodules/TelegramUI/Resources/Animations/message_preview_person_on.json @@ -1 +1 @@ -{"v":"5.9.0","fr":60,"ip":0,"op":20,"w":24,"h":24,"nm":"person_on_24","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"cross","parent":4,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,15,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[8,8],[-8,-8]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1.33,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[600,600],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"icon","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[0]},{"t":10,"s":[100]}],"ix":1},"e":{"a":0,"k":100,"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":1200,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"cross mask","parent":4,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-4.501,19.5,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[7.75,7.75],[-7.75,-7.75]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.654166638851,0.654166638851,0.654166638851,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1.33,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[600,600],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"icon","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[0]},{"t":10,"s":[100]}],"ix":1},"e":{"a":0,"k":100,"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":1200,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"icon","parent":4,"tt":2,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.001,-1.5,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,2.024],[-2.024,0],[0,-2.024],[2.024,0]],"o":[[0,-2.024],[2.024,0],[0,2.024],[-2.024,0]],"v":[[-3.665,-4.25],[0,-7.915],[3.665,-4.25],[0,-0.585]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[1.29,0],[0,-1.29],[-1.29,0],[0,1.29]],"o":[[-1.29,0],[0,1.29],[1.29,0],[0,-1.29]],"v":[[0,-6.585],[-2.335,-4.25],[0,-1.915],[2.335,-4.25]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ind":2,"ty":"sh","ix":3,"ks":{"a":0,"k":{"i":[[-1.142,0.771],[-2.089,0],[-1.144,-0.773],[-0.413,-0.921],[0.532,-0.627],[0.869,0],[0,0],[0.516,0.608],[-0.396,0.881]],"o":[[1.144,-0.773],[2.089,0],[1.142,0.771],[0.396,0.881],[-0.516,0.608],[0,0],[-0.869,0],[-0.532,-0.627],[0.413,-0.921]],"v":[[-4.787,1.865],[0,0.585],[4.787,1.865],[7.031,4.561],[6.699,6.944],[4.5,7.915],[-4.5,7.915],[-6.699,6.944],[-7.031,4.561]],"c":true},"ix":2},"nm":"Path 3","mn":"ADBE Vector Shape - Group","hd":false},{"ind":3,"ty":"sh","ix":4,"ks":{"a":0,"k":{"i":[[0.892,-0.602],[0.335,-0.746],[-0.226,-0.267],[-0.511,0],[0,0],[-0.243,0.286],[0.17,0.378],[0.894,0.604],[1.867,0]],"o":[[-0.894,0.604],[-0.17,0.378],[0.243,0.286],[0,0],[0.511,0],[0.226,-0.267],[-0.335,-0.746],[-0.892,-0.602],[-1.867,0]],"v":[[-4.043,2.968],[-5.818,5.105],[-5.685,6.084],[-4.5,6.585],[4.5,6.585],[5.685,6.084],[5.818,5.105],[4.043,2.968],[0,1.915]],"c":true},"ix":2},"nm":"Path 4","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"Merge Paths 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[600,600],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"icon","np":6,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1200,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":3,"nm":"Rectangle 222","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[12,12,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"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":[16.667,16.667,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":10,"s":[15,15,100]},{"t":19,"s":[16.667,16.667,100]}],"ix":6,"l":2}},"ao":0,"ip":0,"op":1200,"st":0,"bm":0}],"markers":[]} \ No newline at end of file +{"v":"5.9.0","fr":60,"ip":0,"op":20,"w":24,"h":24,"nm":"person_off_24","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"cross","parent":4,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,15,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[8,8],[-8,-8]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1.33,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[600,600],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"icon","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[100]},{"t":10,"s":[0]}],"ix":1},"e":{"a":0,"k":100,"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":1200,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"cross mask","parent":4,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-4.501,19.5,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[7.75,7.75],[-7.75,-7.75]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.654166638851,0.654166638851,0.654166638851,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1.33,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[600,600],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"icon","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[100]},{"t":10,"s":[0]}],"ix":1},"e":{"a":0,"k":100,"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":1200,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"icon","parent":4,"tt":2,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.001,-1.5,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,2.024],[-2.024,0],[0,-2.024],[2.024,0]],"o":[[0,-2.024],[2.024,0],[0,2.024],[-2.024,0]],"v":[[-3.665,-4.25],[0,-7.915],[3.665,-4.25],[0,-0.585]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[1.29,0],[0,-1.29],[-1.29,0],[0,1.29]],"o":[[-1.29,0],[0,1.29],[1.29,0],[0,-1.29]],"v":[[0,-6.585],[-2.335,-4.25],[0,-1.915],[2.335,-4.25]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ind":2,"ty":"sh","ix":3,"ks":{"a":0,"k":{"i":[[-1.142,0.771],[-2.089,0],[-1.144,-0.773],[-0.413,-0.921],[0.532,-0.627],[0.869,0],[0,0],[0.516,0.608],[-0.396,0.881]],"o":[[1.144,-0.773],[2.089,0],[1.142,0.771],[0.396,0.881],[-0.516,0.608],[0,0],[-0.869,0],[-0.532,-0.627],[0.413,-0.921]],"v":[[-4.787,1.865],[0,0.585],[4.787,1.865],[7.031,4.561],[6.699,6.944],[4.5,7.915],[-4.5,7.915],[-6.699,6.944],[-7.031,4.561]],"c":true},"ix":2},"nm":"Path 3","mn":"ADBE Vector Shape - Group","hd":false},{"ind":3,"ty":"sh","ix":4,"ks":{"a":0,"k":{"i":[[0.892,-0.602],[0.335,-0.746],[-0.226,-0.267],[-0.511,0],[0,0],[-0.243,0.286],[0.17,0.378],[0.894,0.604],[1.867,0]],"o":[[-0.894,0.604],[-0.17,0.378],[0.243,0.286],[0,0],[0.511,0],[0.226,-0.267],[-0.335,-0.746],[-0.892,-0.602],[-1.867,0]],"v":[[-4.043,2.968],[-5.818,5.105],[-5.685,6.084],[-4.5,6.585],[4.5,6.585],[5.685,6.084],[5.818,5.105],[4.043,2.968],[0,1.915]],"c":true},"ix":2},"nm":"Path 4","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"Merge Paths 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[600,600],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"icon","np":6,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1200,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":3,"nm":"Rectangle 222","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[12,12,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"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":[16.667,16.667,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":10,"s":[15,15,100]},{"t":19,"s":[16.667,16.667,100]}],"ix":6,"l":2}},"ao":0,"ip":0,"op":1200,"st":0,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/submodules/TelegramUI/Sources/AppDelegate.swift b/submodules/TelegramUI/Sources/AppDelegate.swift index 26d43dd98ec..161fcf20e61 100644 --- a/submodules/TelegramUI/Sources/AppDelegate.swift +++ b/submodules/TelegramUI/Sources/AppDelegate.swift @@ -1,10 +1,10 @@ // MARK: Nicegram imports import AppLovinAdProvider +import FeatTasks import NGAiChat import NGAnalytics import NGAppCache -import var NGCore.ENV -import struct NGCore.Env +import NGCore import NGData import NGEntryPoint import NGEnv @@ -381,20 +381,7 @@ private class UserInterfaceStyleObserverWindow: UIWindow { let mobyApiKey = NGENV.moby_key MobySubscriptionAnalytics.setup(apiKey: mobyApiKey) { account in account.appsflyerID = nil - } completion: { _, produncInfoResult in - if let result = produncInfoResult, - let activeProduct = result.transactions.first(where: { transaction in - if transaction.productID == ENV.premiumProductId { - return [RenewableProductDetails.Status.active, .trial].contains(transaction.status) - } else { - return false - } - }) { - AppCache.currentProductID = activeProduct.productID - } else { - AppCache.currentProductID = nil - } - } + } completion: { _, _ in } if AppCache.appLaunchCount == 1 { let installTime = (time_t)(Date().timeIntervalSince1970) @@ -1215,6 +1202,26 @@ private class UserInterfaceStyleObserverWindow: UIWindow { } }) + // MARK: Nicegram + if #available(iOS 13.0, *) { + let _ = self.context.get().start(next: { context in + if let context = context { + TasksContainer.shared.channelSubscriptionChecker.register { + ChannelSubscriptionCheckerImpl(context: context.context) + } + } + }) + } + + let _ = self.context.get().start(next: { context in + if let context = context { + CoreContainer.shared.urlOpener.register { + UrlOpenerImpl(accountContext: context.context) + } + } + }) + // + self.authContext.set(self.sharedContextPromise.get() |> deliverOnMainQueue |> mapToSignal { sharedApplicationContext -> Signal in @@ -2743,6 +2750,16 @@ private class UserInterfaceStyleObserverWindow: UIWindow { // MARK: Nicegram DB Changes func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) { + // MARK: Nicegram Notifications + let userInfo = response.notification.request.content.userInfo + if let nicegramDeeplink = userInfo["nicegramDeeplink"] as? String, + let url = URL(string: nicegramDeeplink) { + UIApplication.shared.open(url) + completionHandler() + return + } + // + let hiddenIdsAndPasscodeSettings = self.sharedContextPromise.get() |> mapToSignal { context in return context.sharedContext.accountManager.transaction({ transaction -> (hiddenIds: [AccountRecordId], hasMasterPasscode: Bool) in let hiddenIds = transaction.getRecords().filter { $0.attributes.contains(where: { $0.isHiddenAccountAttribute }) }.map { $0.id } @@ -2990,12 +3007,6 @@ private class UserInterfaceStyleObserverWindow: UIWindow { #endif } -// private func fetchPremium() { -// if (isPremium()) { -// validatePremium(true) -// } -// } - private func maybeCheckForUpdates() { #if targetEnvironment(simulator) #else diff --git a/submodules/TelegramUI/Sources/Chat/ChatMessageActionOptions.swift b/submodules/TelegramUI/Sources/Chat/ChatMessageActionOptions.swift index 8e5851a995e..8383797963e 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatMessageActionOptions.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatMessageActionOptions.swift @@ -16,6 +16,7 @@ import TextFormat import ChatMessageItemView import ChatMessageBubbleItemNode import TelegramNotices +import ChatMessageWebpageBubbleContentNode private enum OptionsId: Hashable { case reply @@ -710,7 +711,13 @@ private func chatLinkOptions(selfController: ChatControllerImpl, sourceNode: ASD var largeMedia = false if webpageHasLargeMedia { - largeMedia = urlPreview.largeMedia ?? true + if let value = urlPreview.largeMedia { + largeMedia = value + } else if case let .Loaded(content) = urlPreview.webPage.content { + largeMedia = !defaultWebpageImageSizeIsSmall(webpage: content) + } else { + largeMedia = true + } } else { largeMedia = false } diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index d774683b33c..4e57bc9a5ae 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -4875,6 +4875,65 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self.present(controller, in: .current) })) + }, openPremiumStatusInfo: { [weak self] peerId, sourceView, peerStatus, nameColor in + guard let self else { + return + } + + let context = self.context + let source: Signal + if let peerStatus { + source = context.engine.stickers.resolveInlineStickers(fileIds: [peerStatus]) + |> mapToSignal { files in + if let file = files[peerStatus] { + var reference: StickerPackReference? + for attribute in file.attributes { + if case let .CustomEmoji(_, _, _, packReference) = attribute, let packReference = packReference { + reference = packReference + break + } + } + + if let reference { + return context.engine.stickers.loadedStickerPack(reference: reference, forceActualized: false) + |> filter { result in + if case .result = result { + return true + } else { + return false + } + } + |> take(1) + |> mapToSignal { result -> Signal in + if case let .result(_, items, _) = result { + return .single(.emojiStatus(peerId, peerStatus, items.first?.file, result)) + } else { + return .single(.emojiStatus(peerId, peerStatus, nil, nil)) + } + } + } else { + return .single(.emojiStatus(peerId, peerStatus, nil, nil)) + } + } else { + return .single(.emojiStatus(peerId, peerStatus, nil, nil)) + } + } + } else { + source = .single(.profile(peerId)) + } + + let _ = (source + |> deliverOnMainQueue).startStandalone(next: { [weak self] source in + guard let self else { + return + } + let controller = PremiumIntroScreen(context: self.context, source: source) + controller.sourceView = sourceView + controller.containerView = self.navigationController?.view + controller.animationColor = self.context.peerNameColors.get(nameColor, dark: self.presentationData.theme.overallDarkAppearance).main + self.push(controller) + }) + }, requestMessageUpdate: { [weak self] id, scroll in if let self { self.chatDisplayNode.historyNode.requestMessageUpdate(id, andScrollToItem: scroll) diff --git a/submodules/TelegramUI/Sources/ChatControllerNode.swift b/submodules/TelegramUI/Sources/ChatControllerNode.swift index f8b0b7db849..58fdf565bd4 100644 --- a/submodules/TelegramUI/Sources/ChatControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatControllerNode.swift @@ -6,8 +6,8 @@ import NGRemoteConfig import NGStrings import UndoUI // -// MARK: Nicegram ImagesHub -import FeatImagesHubUI +// MARK: Nicegram ChatBanner +import FeatChatBanner // import Foundation import UIKit @@ -268,8 +268,19 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { }() // - // MARK: Nicegram ImagesHub + // MARK: Nicegram ChatBanner private let ngBannerNode = ASDisplayNode() + private lazy var ngBannerModel = { + if #available(iOS 13.0, *) { + BannerViewModel( + setBannerVisible: { [weak self] visible in + self?.setNgBanner(hidden: !visible) + } + ) + } else { + fatalError() + } + }() // let navigateButtons: ChatHistoryNavigationButtons @@ -866,15 +877,13 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { } // - // MARK: Nicegram ImagesHub + // MARK: Nicegram ChatBanner if #available(iOS 15.0, *), let controller { self.addSubnode(self.ngBannerNode) - ImagesHubUITgHelper.showChatBanner( - view: self.ngBannerNode.view, - controller: controller, - close: { [weak self] in - self?.setNgBanner(hidden: true) - } + ChatBannerTgHelper.show( + viewModel: ngBannerModel, + containerView: self.ngBannerNode.view, + containerController: controller ) } self.ngBannerNode.isHidden = true @@ -1056,20 +1065,31 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { }) } - // MARK: Nicegram ImagesHub + // MARK: Nicegram ChatBanner @available(iOS 15.0, *) private func updateNgBannerVisibility() { guard let peer = self.chatPresentationInterfaceState.renderedPeer?.peer else { return } - let hasPornRestriction = peer.hasPornRestriction( + let isChannel = switch EnginePeer(peer) { + case .channel, .legacyGroup: + true + case .user, .secretChat: + false + } + let isPrivate = (peer.addressName == nil) + let isRestricted = peer.hasPornRestriction( contentSettings: self.context.currentContentSettings.with { $0 } ) - let show = ImagesHubUITgHelper.shouldShowImagesHubInChat() && hasPornRestriction - - setNgBanner(hidden: !show) + ngBannerModel.set( + chatData: ChatData( + isChannel: isChannel, + isPrivate: isPrivate, + isRestricted: isRestricted + ) + ) } private func setNgBanner(hidden: Bool) { @@ -2153,8 +2173,8 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { ) // - // MARK: Nicegram ImagesHub - let bannerHeight = ImagesHubUITgHelper.chatBannerHeight + // MARK: Nicegram ChatBanner + let bannerHeight = ChatBannerTgHelper.bannerHeight transition.updateFrame( node: self.ngBannerNode, @@ -2755,7 +2775,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { showUnblockButton = false } - // MARK: Nicegram ImagesHub + // MARK: Nicegram ChatBanner if #available(iOS 15.0, *) { updateNgBannerVisibility() } diff --git a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift index f17a688f464..8d240579a45 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift @@ -1,8 +1,8 @@ // MARK: Nicegram AiChat import NGAiChatUI // -// MARK: Nicegram ImagesHub -import FeatImagesHubUI +// MARK: Nicegram ChatBanner +import FeatChatBanner // import Foundation import UIKit @@ -443,7 +443,7 @@ public enum ChatHistoryListSource { public final class ChatHistoryListNode: ListView, ChatHistoryNode { static let fixedAdMessageStableId: UInt32 = UInt32.max - 5000 - // MARK: Nicegram ImagesHub + // MARK: Nicegram ChatBanner var showNgBanner = false // @@ -3382,7 +3382,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { additionalBotInset = max(additionalBotInset, 60) } if showNgBanner { - additionalBotInset = max(additionalBotInset, ImagesHubUITgHelper.chatBannerHeight) + additionalBotInset = max(additionalBotInset, ChatBannerTgHelper.bannerHeight) } insets.top += additionalBotInset let updateSizeAndInsets = updateSizeAndInsets.with(insets: insets) diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift index 19a65bc38d2..6f249023d48 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift @@ -29,11 +29,11 @@ import TelegramNotices import ReactionListContextMenuContent import TelegramUIPreferences // MARK: Nicegram Imports +import FeatPremiumUI import struct NGAiChat.AiChatTgHelper import struct NGAiChat.AiContextMenuNotificationPayload import NGAiChatUI import NGCopyProtectedContent -import NGPremiumUI import NGStrings import NGTranslate import NGUI @@ -586,6 +586,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState var loadStickerSaveStatus: MediaId? var loadCopyMediaResource: MediaResource? var isAction = false + var isGiveawayLaunch = false var diceEmoji: String? if messages.count == 1 { for media in messages[0].media { @@ -600,6 +601,9 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState } } else if media is TelegramMediaAction || media is TelegramMediaExpiredContent { isAction = true + if let action = media as? TelegramMediaAction, case .giveawayLaunched = action.action { + isGiveawayLaunch = true + } } else if let image = media as? TelegramMediaImage { if !messages[0].containsSecretMedia { loadCopyMediaResource = largestImageRepresentation(image.representations)?.resource @@ -660,6 +664,10 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState canPin = false } + if isGiveawayLaunch { + canReply = false + } + if let peer = messages[0].peers[messages[0].id.peerId] { if peer.isDeleted { canPin = false @@ -946,7 +954,6 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState resourceAvailable = false } - if !isPremium && isDownloading { var isLargeFile = false for media in message.media { diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateContextQueries.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateContextQueries.swift index 5530c724e2f..045bc803f39 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateContextQueries.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateContextQueries.swift @@ -548,7 +548,7 @@ func urlPreviewStateForInputText(_ inputText: NSAttributedString?, context: Acco } if let _ = dataDetector { let detectedUrls = detectUrls(inputText) - if detectedUrls != currentQuery?.detectedUrls { + if detectedUrls != (currentQuery?.detectedUrls ?? []) { if !detectedUrls.isEmpty { return (UrlPreviewState(detectedUrls: detectedUrls), webpagePreview(account: context.account, urls: detectedUrls) |> mapToSignal { result -> Signal<(TelegramMediaWebpage, String)?, NoError> in diff --git a/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift b/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift index 04eda7684bb..320fa27d16a 100644 --- a/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift @@ -583,19 +583,25 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode { var updatedMediaReference: AnyMediaReference? var imageDimensions: CGSize? + let giveaway = pinnedMessage.message.media.first(where: { $0 is TelegramMediaGiveaway }) as? TelegramMediaGiveaway + var titleStrings: [AnimatedCountLabelNode.Segment] = [] - if pinnedMessage.totalCount == 2 { - if pinnedMessage.index == 0 { - titleStrings.append(.text(0, NSAttributedString(string: "\(strings.Conversation_PinnedPreviousMessage) ", font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlAccentColor))) + if let _ = giveaway { + titleStrings.append(.text(0, NSAttributedString(string: "\(strings.Conversation_PinnedGiveaway) ", font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlAccentColor))) + } else { + if pinnedMessage.totalCount == 2 { + if pinnedMessage.index == 0 { + titleStrings.append(.text(0, NSAttributedString(string: "\(strings.Conversation_PinnedPreviousMessage) ", font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlAccentColor))) + } else { + titleStrings.append(.text(0, NSAttributedString(string: "\(strings.Conversation_PinnedMessage) ", font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlAccentColor))) + } + } else if pinnedMessage.totalCount > 1 && pinnedMessage.index != pinnedMessage.totalCount - 1 { + titleStrings.append(.text(0, NSAttributedString(string: "\(strings.Conversation_PinnedMessage)", font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlAccentColor))) + titleStrings.append(.text(1, NSAttributedString(string: " #", font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlAccentColor))) + titleStrings.append(.number(pinnedMessage.index + 1, NSAttributedString(string: "\(pinnedMessage.index + 1)", font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlAccentColor))) } else { titleStrings.append(.text(0, NSAttributedString(string: "\(strings.Conversation_PinnedMessage) ", font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlAccentColor))) } - } else if pinnedMessage.totalCount > 1 && pinnedMessage.index != pinnedMessage.totalCount - 1 { - titleStrings.append(.text(0, NSAttributedString(string: "\(strings.Conversation_PinnedMessage)", font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlAccentColor))) - titleStrings.append(.text(1, NSAttributedString(string: " #", font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlAccentColor))) - titleStrings.append(.number(pinnedMessage.index + 1, NSAttributedString(string: "\(pinnedMessage.index + 1)", font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlAccentColor))) - } else { - titleStrings.append(.text(0, NSAttributedString(string: "\(strings.Conversation_PinnedMessage) ", font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlAccentColor))) } if !message.containsSecretMedia { @@ -679,7 +685,20 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode { let messageText: NSAttributedString let textFont = Font.regular(15.0) - if isText { + if let giveaway { + let dateString = stringForDateWithoutYear(date: Date(timeIntervalSince1970: TimeInterval(giveaway.untilDate)), timeZone: .current, strings: strings) + let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970) + let isFinished = currentTime >= giveaway.untilDate + let text: String + if isFinished { + let winnersString = strings.Conversation_PinnedGiveaway_Finished_Winners(giveaway.quantity) + text = strings.Conversation_PinnedGiveaway_Finished(winnersString, dateString).string + } else { + let winnersString = strings.Conversation_PinnedGiveaway_Ongoing_Winners(giveaway.quantity) + text = strings.Conversation_PinnedGiveaway_Ongoing(winnersString, dateString).string + } + messageText = NSAttributedString(string: text, font: textFont, textColor: theme.chat.inputPanel.primaryTextColor) + } else if isText { var text = message.text var messageEntities = message.textEntitiesAttribute?.entities ?? [] diff --git a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift index 24d780d5209..6abb714ff05 100644 --- a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift @@ -3791,17 +3791,19 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch } else { var children: [UIAction] = [] - children.append(UIAction(title: self.strings?.TextFormat_Quote ?? "Quote", image: nil) { [weak self] (action) in - if let strongSelf = self { - strongSelf.formatAttributesQuote(strongSelf) - } - }) - var hasSpoilers = true if self.presentationInterfaceState?.chatLocation.peerId?.namespace == Namespaces.Peer.SecretChat { hasSpoilers = false } + if hasSpoilers { + children.append(UIAction(title: self.strings?.TextFormat_Quote ?? "Quote", image: nil) { [weak self] (action) in + if let strongSelf = self { + strongSelf.formatAttributesQuote(strongSelf) + } + }) + } + if hasSpoilers { children.append(UIAction(title: self.strings?.TextFormat_Spoiler ?? "Spoiler", image: nil) { [weak self] (action) in if let strongSelf = self { diff --git a/submodules/TelegramUI/Sources/MentionChatInputContextPanelNode.swift b/submodules/TelegramUI/Sources/MentionChatInputContextPanelNode.swift index a740df5b28b..cad3ddcb940 100644 --- a/submodules/TelegramUI/Sources/MentionChatInputContextPanelNode.swift +++ b/submodules/TelegramUI/Sources/MentionChatInputContextPanelNode.swift @@ -1,6 +1,6 @@ // MARK: Nicegram Imports +import FeatPremiumUI import NGData -import NGPremiumUI // import Foundation import UIKit diff --git a/submodules/TelegramUI/Sources/Nicegram/ChannelSubscriptionCheckerImpl.swift b/submodules/TelegramUI/Sources/Nicegram/ChannelSubscriptionCheckerImpl.swift new file mode 100644 index 00000000000..43e575e313e --- /dev/null +++ b/submodules/TelegramUI/Sources/Nicegram/ChannelSubscriptionCheckerImpl.swift @@ -0,0 +1,47 @@ +import AccountContext +import FeatTasks +import SwiftSignalKit +import TelegramCore + +@available(iOS 13.0, *) +class ChannelSubscriptionCheckerImpl { + + // MARK: - Dependencies + + private let context: AccountContext + + // MARK: - Lifecycle + + init(context: AccountContext) { + self.context = context + } +} + +@available(iOS 13.0, *) +extension ChannelSubscriptionCheckerImpl: ChannelSubscriptionChecker { + public func isSubscribed(to id: ChannelId) async -> Bool { + await withCheckedContinuation { continuation in + _ = (context.engine.peers.resolvePeerByName(name: id) + |> mapToSignal { result -> Signal in + guard case let .result(result) = result else { + return .complete() + } + return .single(result) + } + |> deliverOnMainQueue) + .start(next: { peer in + guard case let .channel(channel) = peer else { + continuation.resume(returning: false) + return + } + + switch channel.participationStatus { + case .member: + continuation.resume(returning: true) + case .left, .kicked: + continuation.resume(returning: false) + } + }) + } + } +} diff --git a/submodules/TelegramUI/Sources/NGDeeplinkHandler.swift b/submodules/TelegramUI/Sources/Nicegram/NGDeeplinkHandler.swift similarity index 92% rename from submodules/TelegramUI/Sources/NGDeeplinkHandler.swift rename to submodules/TelegramUI/Sources/Nicegram/NGDeeplinkHandler.swift index 8bf99134c2d..e10eaa4ff3e 100644 --- a/submodules/TelegramUI/Sources/NGDeeplinkHandler.swift +++ b/submodules/TelegramUI/Sources/Nicegram/NGDeeplinkHandler.swift @@ -2,6 +2,9 @@ import Foundation import AccountContext import Display import FeatImagesHubUI +import FeatPremiumUI +import FeatRewardsUI +import FeatTasks import NGAiChatUI import NGAnalytics import NGAssistantUI @@ -11,7 +14,6 @@ import class NGCoreUI.SharedLoadingView import NGModels import NGOnboarding import NGRemoteConfig -import NGPremiumUI import NGSpecialOffer import NGUI import TelegramPresentationData @@ -66,6 +68,13 @@ class NGDeeplinkHandler { return handleNicegramPremium(url: url) case "onboarding": return handleOnboarding(url: url) + case "profit": + if #available(iOS 15.0, *) { + Task { @MainActor in + RewardsUITgHelper.showRewards() + } + } + return true case "specialOffer": if #available(iOS 13.0, *) { return handleSpecialOffer(url: url) @@ -74,6 +83,12 @@ class NGDeeplinkHandler { } case "pstAuth": return handlePstAuth(url: url) + case "task": + if #available(iOS 15.0, *) { + let taskDeeplinkHandler = TasksContainer.shared.taskDeeplinkHandler() + taskDeeplinkHandler.handle(url) + } + return true default: return false } diff --git a/submodules/TelegramUI/Sources/Nicegram/UrlOpenerImpl.swift b/submodules/TelegramUI/Sources/Nicegram/UrlOpenerImpl.swift new file mode 100644 index 00000000000..03168fe5980 --- /dev/null +++ b/submodules/TelegramUI/Sources/Nicegram/UrlOpenerImpl.swift @@ -0,0 +1,40 @@ +import AccountContext +import Display +import Foundation +import NGCore + +class UrlOpenerImpl { + + // MARK: - Dependencies + + private let accountContext: AccountContext + + // MARK: - Lifecycle + + init(accountContext: AccountContext) { + self.accountContext = accountContext + } +} + +extension UrlOpenerImpl: UrlOpener { + func open(_ url: URL) { + let sharedContext = accountContext.sharedContext + let navigationController = sharedContext.mainWindow?.viewController as? NavigationController + let presentationData = sharedContext.currentPresentationData.with { $0 } + + let telegramHosts = ["t.me", "telegram.me"] + let isTelegramHost = telegramHosts.contains(url._wrapperHost() ?? "") + + let forceExternal = !isTelegramHost + + sharedContext.openExternalUrl( + context: accountContext, + urlContext: .generic, + url: url.absoluteString, + forceExternal: forceExternal, + presentationData: presentationData, + navigationController: navigationController, + dismissInput: {} + ) + } +} diff --git a/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift b/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift index 6a0ef980926..4a656b7a493 100644 --- a/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift +++ b/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift @@ -170,6 +170,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu }, saveMediaToFiles: { _ in }, openNoAdsDemo: { }, displayGiveawayParticipationStatus: { _ in + }, openPremiumStatusInfo: { _, _, _, _ in }, requestMessageUpdate: { _, _ in }, cancelInteractiveKeyboardGestures: { }, dismissTextInput: { diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift index 3a6e6b571e7..7d48eff94ac 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift @@ -48,6 +48,7 @@ import TelegramNotices import SaveToCameraRoll import PeerInfoUI // MARK: Nicegram Imports +import FeatPremiumUI import NGAiChatUI import NGWebUtils import NGStrings @@ -56,7 +57,6 @@ import NGData import NGEnv import NGLab import UndoUI -import NGPremiumUI // import ListMessageItem import GalleryData @@ -550,6 +550,7 @@ private final class PeerInfoInteraction { let editingToggleMessageSignatures: (Bool) -> Void let openParticipantsSection: (PeerInfoParticipantsSection) -> Void let openRecentActions: () -> Void + let openStats: () -> Void let editingOpenPreHistorySetup: () -> Void let editingOpenAutoremoveMesages: () -> Void let openPermissions: () -> Void @@ -604,6 +605,7 @@ private final class PeerInfoInteraction { editingToggleMessageSignatures: @escaping (Bool) -> Void, openParticipantsSection: @escaping (PeerInfoParticipantsSection) -> Void, openRecentActions: @escaping () -> Void, + openStats: @escaping () -> Void, editingOpenPreHistorySetup: @escaping () -> Void, editingOpenAutoremoveMesages: @escaping () -> Void, openPermissions: @escaping () -> Void, @@ -657,6 +659,7 @@ private final class PeerInfoInteraction { self.editingToggleMessageSignatures = editingToggleMessageSignatures self.openParticipantsSection = openParticipantsSection self.openRecentActions = openRecentActions + self.openStats = openStats self.editingOpenPreHistorySetup = editingOpenPreHistorySetup self.editingOpenAutoremoveMesages = editingOpenAutoremoveMesages self.openPermissions = openPermissions @@ -1681,8 +1684,9 @@ private func editingItems(data: PeerInfoScreenData?, state: PeerInfoState, chatL let ItemAdmins = 9 let ItemMembers = 10 let ItemMemberRequests = 11 - let ItemBanned = 12 - let ItemRecentActions = 13 + let ItemStats = 12 + let ItemBanned = 13 + let ItemRecentActions = 14 let isCreator = channel.flags.contains(.isCreator) @@ -1772,47 +1776,56 @@ private func editingItems(data: PeerInfoScreenData?, state: PeerInfoState, chatL } var canEditMembers = false - if channel.hasPermission(.banMembers) { + if channel.hasPermission(.banMembers) && (channel.adminRights != nil || channel.flags.contains(.isCreator)) { canEditMembers = true } if canEditMembers { - if channel.adminRights != nil || channel.flags.contains(.isCreator) { - let adminCount: Int32 - let memberCount: Int32 - let bannedCount: Int32 - if let cachedData = data.cachedData as? CachedChannelData { - adminCount = cachedData.participantsSummary.adminCount ?? 0 - memberCount = cachedData.participantsSummary.memberCount ?? 0 - bannedCount = cachedData.participantsSummary.kickedCount ?? 0 - } else { - adminCount = 0 - memberCount = 0 - bannedCount = 0 - } - - items[.peerAdditionalSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemAdmins, label: .text("\(adminCount == 0 ? "" : "\(presentationStringsFormattedNumber(adminCount, presentationData.dateTimeFormat.groupingSeparator))")"), text: presentationData.strings.GroupInfo_Administrators, icon: UIImage(bundleImageName: "Chat/Info/GroupAdminsIcon"), action: { - interaction.openParticipantsSection(.admins) - })) - items[.peerAdditionalSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemMembers, label: .text("\(memberCount == 0 ? "" : "\(presentationStringsFormattedNumber(memberCount, presentationData.dateTimeFormat.groupingSeparator))")"), text: presentationData.strings.Channel_Info_Subscribers, icon: UIImage(bundleImageName: "Chat/Info/GroupMembersIcon"), action: { - interaction.openParticipantsSection(.members) - })) - - if let count = data.requests?.count, count > 0 { - items[.peerAdditionalSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemMemberRequests, label: .badge(presentationStringsFormattedNumber(count, presentationData.dateTimeFormat.groupingSeparator), presentationData.theme.list.itemAccentColor), text: presentationData.strings.GroupInfo_MemberRequests, icon: UIImage(bundleImageName: "Chat/Info/GroupRequestsIcon"), action: { - interaction.openParticipantsSection(.memberRequests) - })) - } - - items[.peerAdditionalSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemBanned, label: .text("\(bannedCount == 0 ? "" : "\(presentationStringsFormattedNumber(bannedCount, presentationData.dateTimeFormat.groupingSeparator))")"), text: presentationData.strings.GroupInfo_Permissions_Removed, icon: UIImage(bundleImageName: "Chat/Info/GroupRemovedIcon"), action: { - interaction.openParticipantsSection(.banned) - })) - - items[.peerAdditionalSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemRecentActions, label: .none, text: presentationData.strings.Group_Info_AdminLog, icon: UIImage(bundleImageName: "Chat/Info/RecentActionsIcon"), action: { - interaction.openRecentActions() + let adminCount: Int32 + let memberCount: Int32 + if let cachedData = data.cachedData as? CachedChannelData { + adminCount = cachedData.participantsSummary.adminCount ?? 0 + memberCount = cachedData.participantsSummary.memberCount ?? 0 + } else { + adminCount = 0 + memberCount = 0 + } + + items[.peerAdditionalSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemAdmins, label: .text("\(adminCount == 0 ? "" : "\(presentationStringsFormattedNumber(adminCount, presentationData.dateTimeFormat.groupingSeparator))")"), text: presentationData.strings.GroupInfo_Administrators, icon: UIImage(bundleImageName: "Chat/Info/GroupAdminsIcon"), action: { + interaction.openParticipantsSection(.admins) + })) + items[.peerAdditionalSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemMembers, label: .text("\(memberCount == 0 ? "" : "\(presentationStringsFormattedNumber(memberCount, presentationData.dateTimeFormat.groupingSeparator))")"), text: presentationData.strings.Channel_Info_Subscribers, icon: UIImage(bundleImageName: "Chat/Info/GroupMembersIcon"), action: { + interaction.openParticipantsSection(.members) + })) + + if let count = data.requests?.count, count > 0 { + items[.peerAdditionalSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemMemberRequests, label: .badge(presentationStringsFormattedNumber(count, presentationData.dateTimeFormat.groupingSeparator), presentationData.theme.list.itemAccentColor), text: presentationData.strings.GroupInfo_MemberRequests, icon: UIImage(bundleImageName: "Chat/Info/GroupRequestsIcon"), action: { + interaction.openParticipantsSection(.memberRequests) })) } } + 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() + })) + } + + if canEditMembers { + let bannedCount: Int32 + if let cachedData = data.cachedData as? CachedChannelData { + bannedCount = cachedData.participantsSummary.kickedCount ?? 0 + } else { + bannedCount = 0 + } + items[.peerAdditionalSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemBanned, label: .text("\(bannedCount == 0 ? "" : "\(presentationStringsFormattedNumber(bannedCount, presentationData.dateTimeFormat.groupingSeparator))")"), text: presentationData.strings.GroupInfo_Permissions_Removed, icon: UIImage(bundleImageName: "Chat/Info/GroupRemovedIcon"), action: { + interaction.openParticipantsSection(.banned) + })) + + items[.peerAdditionalSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemRecentActions, label: .none, text: presentationData.strings.Group_Info_AdminLog, icon: UIImage(bundleImageName: "Chat/Info/RecentActionsIcon"), action: { + interaction.openRecentActions() + })) + } + if isCreator { //if let cachedData = data.cachedData as? CachedChannelData, cachedData.flags.contains(.canDeleteHistory) { items[.peerActions]!.append(PeerInfoScreenActionItem(id: ItemDeleteChannel, text: presentationData.strings.ChannelInfo_DeleteChannel, color: .destructive, icon: nil, alignment: .natural, action: { interaction.openDeletePeer() @@ -2438,6 +2451,9 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro openRecentActions: { [weak self] in self?.openRecentActions() }, + openStats: { [weak self] in + self?.openStats() + }, editingOpenPreHistorySetup: { [weak self] in self?.editingOpenPreHistorySetup() }, @@ -3055,6 +3071,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro }, saveMediaToFiles: { _ in }, openNoAdsDemo: { }, displayGiveawayParticipationStatus: { _ in + }, openPremiumStatusInfo: { _, _, _, _ in }, requestMessageUpdate: { _, _ in }, cancelInteractiveKeyboardGestures: { }, dismissTextInput: { @@ -3928,37 +3945,31 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro return } - let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: strongSelf.context.account.peerId)) - |> deliverOnMainQueue).startStandalone(next: { [weak self] _ in + let source: Signal + if let peerStatus = peerStatus { + source = emojiStatusFileAndPack + |> take(1) + |> mapToSignal { emojiStatusFileAndPack -> Signal in + if let (file, pack) = emojiStatusFileAndPack { + return .single(.emojiStatus(strongSelf.peerId, peerStatus.fileId, file, pack)) + } else { + return .complete() + } + } + } else { + source = .single(.profile(strongSelf.peerId)) + } + + let _ = (source + |> deliverOnMainQueue).startStandalone(next: { [weak self] source in guard let strongSelf = self else { return } - let source: Signal - if let peerStatus = peerStatus { - source = emojiStatusFileAndPack - |> take(1) - |> mapToSignal { emojiStatusFileAndPack -> Signal in - if let (file, pack) = emojiStatusFileAndPack { - return .single(.emojiStatus(strongSelf.peerId, peerStatus.fileId, file, pack)) - } else { - return .complete() - } - } - } else { - source = .single(.profile(strongSelf.peerId)) - } - - let _ = (source - |> deliverOnMainQueue).startStandalone(next: { [weak self] source in - guard let strongSelf = self else { - return - } - let controller = PremiumIntroScreen(context: strongSelf.context, source: source) - controller.sourceView = sourceView - controller.containerView = strongSelf.controller?.navigationController?.view - controller.animationColor = white ? .white : strongSelf.presentationData.theme.list.itemAccentColor - strongSelf.controller?.push(controller) - }) + let controller = PremiumIntroScreen(context: strongSelf.context, source: source) + controller.sourceView = sourceView + controller.containerView = strongSelf.controller?.navigationController?.view + controller.animationColor = white ? .white : strongSelf.presentationData.theme.list.itemAccentColor + strongSelf.controller?.push(controller) }) } diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index ed3723c6148..1ca659d23f1 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -1693,6 +1693,7 @@ public final class SharedAccountContextImpl: SharedAccountContext { }, saveMediaToFiles: { _ in }, openNoAdsDemo: { }, displayGiveawayParticipationStatus: { _ in + }, openPremiumStatusInfo: { _, _, _, _ in }, requestMessageUpdate: { _, _ in }, cancelInteractiveKeyboardGestures: { }, dismissTextInput: { diff --git a/submodules/TextFormat/Sources/StringWithAppliedEntities.swift b/submodules/TextFormat/Sources/StringWithAppliedEntities.swift index 63b88620a29..7eed41bb7d1 100644 --- a/submodules/TextFormat/Sources/StringWithAppliedEntities.swift +++ b/submodules/TextFormat/Sources/StringWithAppliedEntities.swift @@ -215,9 +215,11 @@ public func stringWithAppliedEntities(_ text: String, entities: [MessageTextEnti nsString = text as NSString } if let codeBlockTitleColor, let codeBlockAccentColor, let codeBlockBackgroundColor { - string.addAttribute(NSAttributedString.Key(rawValue: "Attribute__Blockquote"), value: TextNodeBlockQuoteData(kind: .code(language: language), title: language.flatMap { - NSAttributedString(string: $0.capitalized, font: boldFont.withSize(round(boldFont.pointSize * 0.8235294117647058)), textColor: codeBlockTitleColor) - }, color: codeBlockAccentColor, secondaryColor: nil, tertiaryColor: nil, backgroundColor: codeBlockBackgroundColor), range: range) + var title: NSAttributedString? + 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) } case .BlockQuote: addFontAttributes(range, .blockQuote) diff --git a/swift_deps.bzl b/swift_deps.bzl index 0844eaad60b..60827c08d44 100644 --- a/swift_deps.bzl +++ b/swift_deps.bzl @@ -12,7 +12,7 @@ def swift_dependencies(): # version: 2.6.1 swift_package( name = "swiftpkg_floatingpanel", - commit = "dd238884bf85e96a4c6c2703fc2fc1ff0f5c7494", + commit = "5b33d3d5ff1f50f4a2d64158ccfe8c07b5a3e649", dependencies_index = "@//:swift_deps_index.json", remote = "https://github.com/scenee/FloatingPanel", ) @@ -36,7 +36,7 @@ def swift_dependencies(): # branch: develop swift_package( name = "swiftpkg_nicegram_assistant_ios", - commit = "74a903bbc75af22ef19aa77ab20ba8df1ae0de27", + commit = "c3686ba5c4f4ad5eb0273bc9dfd5784e0971a5a2", dependencies_index = "@//:swift_deps_index.json", remote = "git@bitbucket.org:mobyrix/nicegram-assistant-ios.git", ) @@ -52,7 +52,7 @@ def swift_dependencies(): # version: 5.15.5 swift_package( name = "swiftpkg_sdwebimage", - commit = "fd1950de05a5ad77cb252fd88576c1e1809ee50d", + commit = "1b9a2e902cbde5fdf362faa0f4fd76ea74d74305", dependencies_index = "@//:swift_deps_index.json", remote = "https://github.com/SDWebImage/SDWebImage.git", ) diff --git a/swift_deps_index.json b/swift_deps_index.json index 52fb30027d3..6196b008b80 100644 --- a/swift_deps_index.json +++ b/swift_deps_index.json @@ -57,6 +57,36 @@ "nicegram-assistant" ] }, + { + "name": "FeatAmbassadors", + "c99name": "FeatAmbassadors", + "src_type": "swift", + "label": "@swiftpkg_nicegram_assistant_ios//:Sources_FeatAmbassadors", + "package_identity": "nicegram-assistant-ios", + "product_memberships": [ + "nicegram-assistant" + ] + }, + { + "name": "FeatBilling", + "c99name": "FeatBilling", + "src_type": "swift", + "label": "@swiftpkg_nicegram_assistant_ios//:Sources_FeatBilling", + "package_identity": "nicegram-assistant-ios", + "product_memberships": [ + "nicegram-assistant" + ] + }, + { + "name": "FeatChatBanner", + "c99name": "FeatChatBanner", + "src_type": "swift", + "label": "@swiftpkg_nicegram_assistant_ios//:Sources_FeatChatBanner", + "package_identity": "nicegram-assistant-ios", + "product_memberships": [ + "nicegram-assistant" + ] + }, { "name": "FeatImagesHub", "c99name": "FeatImagesHub", @@ -97,6 +127,66 @@ "nicegram-assistant" ] }, + { + "name": "FeatPremium", + "c99name": "FeatPremium", + "src_type": "swift", + "label": "@swiftpkg_nicegram_assistant_ios//:Sources_FeatPremium", + "package_identity": "nicegram-assistant-ios", + "product_memberships": [ + "nicegram-assistant" + ] + }, + { + "name": "FeatPremiumUI", + "c99name": "FeatPremiumUI", + "src_type": "swift", + "label": "@swiftpkg_nicegram_assistant_ios//:Sources_FeatPremiumUI", + "package_identity": "nicegram-assistant-ios", + "product_memberships": [ + "nicegram-assistant" + ] + }, + { + "name": "FeatRewards", + "c99name": "FeatRewards", + "src_type": "swift", + "label": "@swiftpkg_nicegram_assistant_ios//:Sources_FeatRewards", + "package_identity": "nicegram-assistant-ios", + "product_memberships": [ + "nicegram-assistant" + ] + }, + { + "name": "FeatRewardsUI", + "c99name": "FeatRewardsUI", + "src_type": "swift", + "label": "@swiftpkg_nicegram_assistant_ios//:Sources_FeatRewardsUI", + "package_identity": "nicegram-assistant-ios", + "product_memberships": [ + "nicegram-assistant" + ] + }, + { + "name": "FeatTasks", + "c99name": "FeatTasks", + "src_type": "swift", + "label": "@swiftpkg_nicegram_assistant_ios//:Sources_FeatTasks", + "package_identity": "nicegram-assistant-ios", + "product_memberships": [ + "nicegram-assistant" + ] + }, + { + "name": "FeatTasksUI", + "c99name": "FeatTasksUI", + "src_type": "swift", + "label": "@swiftpkg_nicegram_assistant_ios//:Sources_FeatTasksUI", + "package_identity": "nicegram-assistant-ios", + "product_memberships": [ + "nicegram-assistant" + ] + }, { "name": "NGAiChat", "c99name": "NGAiChat", @@ -238,26 +328,6 @@ "nicegram-assistant" ] }, - { - "name": "NGPremium", - "c99name": "NGPremium", - "src_type": "swift", - "label": "@swiftpkg_nicegram_assistant_ios//:Sources_NGPremium", - "package_identity": "nicegram-assistant-ios", - "product_memberships": [ - "nicegram-assistant" - ] - }, - { - "name": "NGPremiumUI", - "c99name": "NGPremiumUI", - "src_type": "swift", - "label": "@swiftpkg_nicegram_assistant_ios//:Sources_NGPremiumUI", - "package_identity": "nicegram-assistant-ios", - "product_memberships": [ - "nicegram-assistant" - ] - }, { "name": "NGRepoTg", "c99name": "NGRepoTg", @@ -555,14 +625,15 @@ "name": "nicegram-assistant", "type": "library", "target_labels": [ + "@swiftpkg_nicegram_assistant_ios//:Sources_FeatChatBanner", "@swiftpkg_nicegram_assistant_ios//:Sources_FeatImagesHubUI", "@swiftpkg_nicegram_assistant_ios//:Sources_FeatPartners", "@swiftpkg_nicegram_assistant_ios//:Sources_FeatPinnedChats", + "@swiftpkg_nicegram_assistant_ios//:Sources_FeatPremiumUI", "@swiftpkg_nicegram_assistant_ios//:Sources_NGAiChatUI", "@swiftpkg_nicegram_assistant_ios//:Sources_NGAssistantUI", "@swiftpkg_nicegram_assistant_ios//:Sources_NGCardUI", - "@swiftpkg_nicegram_assistant_ios//:Sources_NGEntryPoint", - "@swiftpkg_nicegram_assistant_ios//:Sources_NGPremiumUI" + "@swiftpkg_nicegram_assistant_ios//:Sources_NGEntryPoint" ] }, { @@ -700,9 +771,9 @@ "name": "swiftpkg_floatingpanel", "identity": "floatingpanel", "remote": { - "commit": "dd238884bf85e96a4c6c2703fc2fc1ff0f5c7494", + "commit": "5b33d3d5ff1f50f4a2d64158ccfe8c07b5a3e649", "remote": "https://github.com/scenee/FloatingPanel", - "version": "2.8.0" + "version": "2.8.1" } }, { @@ -727,7 +798,7 @@ "name": "swiftpkg_nicegram_assistant_ios", "identity": "nicegram-assistant-ios", "remote": { - "commit": "74a903bbc75af22ef19aa77ab20ba8df1ae0de27", + "commit": "c3686ba5c4f4ad5eb0273bc9dfd5784e0971a5a2", "remote": "git@bitbucket.org:mobyrix/nicegram-assistant-ios.git", "branch": "develop" } @@ -745,9 +816,9 @@ "name": "swiftpkg_sdwebimage", "identity": "sdwebimage", "remote": { - "commit": "fd1950de05a5ad77cb252fd88576c1e1809ee50d", + "commit": "1b9a2e902cbde5fdf362faa0f4fd76ea74d74305", "remote": "https://github.com/SDWebImage/SDWebImage.git", - "version": "5.18.4" + "version": "5.18.5" } }, { diff --git a/versions.json b/versions.json index efe9a6cee56..aa5c3d33a2c 100644 --- a/versions.json +++ b/versions.json @@ -1,5 +1,5 @@ { - "app": "1.4.6", + "app": "1.4.7", "bazel": "6.4.0", "xcode": "15.0.1" }