diff --git a/BUCK b/BUCK new file mode 100644 index 0000000000..ea67fb9dd5 --- /dev/null +++ b/BUCK @@ -0,0 +1,553 @@ +load("//Config:utils.bzl", + "library_configs", +) + +load("//Config:configs.bzl", + "app_binary_configs", + "share_extension_configs", + "widget_extension_configs", + "notification_content_extension_configs", + "notification_service_extension_configs", + "intents_extension_configs", + "watch_extension_binary_configs", + "watch_binary_configs", + "info_plist_substitutions", + "app_info_plist_substitutions", + "share_extension_info_plist_substitutions", + "widget_extension_info_plist_substitutions", + "notification_content_extension_info_plist_substitutions", + "notification_service_extension_info_plist_substitutions", + "intents_extension_info_plist_substitutions", + "watch_extension_info_plist_substitutions", + "watch_info_plist_substitutions", + "DEVELOPMENT_LANGUAGE", +) + +load("//Config:buck_rule_macros.bzl", + "apple_lib", + "framework_binary_dependencies", + "framework_bundle_dependencies", + "glob_map", + "glob_sub_map", + "merge_maps", +) + +framework_dependencies = [ + "//submodules/MtProtoKit:MtProtoKit", + "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", + "//submodules/Postbox:Postbox", + "//submodules/TelegramApi:TelegramApi", + "//submodules/SyncCore:SyncCore", + "//submodules/TelegramCore:TelegramCore", + "//submodules/AsyncDisplayKit:AsyncDisplayKit", + "//submodules/Display:Display", + "//submodules/TelegramUI:TelegramUI", +] + +resource_dependencies = [ + "//submodules/LegacyComponents:LegacyComponentsResources", + "//submodules/TelegramUI:TelegramUIAssets", + "//submodules/TelegramUI:TelegramUIResources", + #"//submodules/WalletUI:WalletUIAssets", + #"//submodules/WalletUI:WalletUIResources", + "//submodules/PasswordSetupUI:PasswordSetupUIResources", + "//submodules/PasswordSetupUI:PasswordSetupUIAssets", + "//submodules/OverlayStatusController:OverlayStatusControllerResources", + "//:AppResources", + "//:AppStringResources", + "//:InfoPlistStringResources", + "//:AppIntentVocabularyResources", + "//:Icons", + "//:AdditionalIcons", + "//:LaunchScreen", +] + +build_phase_scripts = [ +] + +apple_resource( + name = "AppResources", + files = glob([ + "Telegram-iOS/Resources/**/*", + ], exclude = ["Telegram-iOS/Resources/**/.*"]), + visibility = ["PUBLIC"], +) + +apple_resource( + name = "AppStringResources", + files = [], + variants = glob([ + "Telegram-iOS/*.lproj/Localizable.strings", + ]), + visibility = ["PUBLIC"], +) + +apple_resource( + name = "AppIntentVocabularyResources", + files = [], + variants = glob([ + "Telegram-iOS/*.lproj/AppIntentVocabulary.plist", + ]), + visibility = ["PUBLIC"], +) + +apple_resource( + name = "InfoPlistStringResources", + files = [], + variants = glob([ + "Telegram-iOS/*.lproj/InfoPlist.strings", + ]), + visibility = ["PUBLIC"], +) + +apple_asset_catalog( + name = "Icons", + dirs = [ + "Telegram-iOS/Icons.xcassets", + "Telegram-iOS/AppIcons.xcassets", + ], + app_icon = "AppIconLLC", + visibility = ["PUBLIC"], +) + +apple_resource( + name = "AdditionalIcons", + files = glob([ + "Telegram-iOS/*.png", + ]), + visibility = ["PUBLIC"], +) + +apple_resource( + name = "LaunchScreen", + files = [ + "Telegram-iOS/Base.lproj/LaunchScreen.xib", + ], + visibility = ["PUBLIC"], +) + +apple_library( + name = "AppLibrary", + visibility = [ + "//:", + "//...", + ], + configs = library_configs(), + swift_version = native.read_config("swift", "version"), + srcs = [ + "Telegram-iOS/main.m", + "Telegram-iOS/Application.swift" + ], + deps = [ + ] + + framework_binary_dependencies(framework_dependencies), +) + +apple_binary( + name = "AppBinary", + visibility = [ + "//:", + "//...", + ], + configs = app_binary_configs(), + swift_version = native.read_config("swift", "version"), + srcs = [ + "SupportFiles/Empty.swift", + ], + deps = [ + ":AppLibrary", + ] + + resource_dependencies, +) + +apple_bundle( + name = "Telegram", + visibility = [ + "//:", + ], + extension = "app", + binary = ":AppBinary", + product_name = "Telegram", + info_plist = "Telegram-iOS/Info.plist", + info_plist_substitutions = app_info_plist_substitutions(), + deps = [ + ":ShareExtension", + ":WidgetExtension", + ":NotificationContentExtension", + ":NotificationServiceExtension", + ":IntentsExtension", + ":WatchApp#watch", + ] + + framework_bundle_dependencies(framework_dependencies), +) + +# Share Extension + +apple_binary( + name = "ShareBinary", + srcs = glob([ + "Share/**/*.swift", + ]), + configs = share_extension_configs(), + linker_flags = [ + "-e", + "_NSExtensionMain", + "-Xlinker", + "-rpath", + "-Xlinker", + "/usr/lib/swift", + "-Xlinker", + "-rpath", + "-Xlinker", + "@executable_path/../../Frameworks", + ], + deps = [ + "//submodules/TelegramUI:TelegramUI#shared", + ], + frameworks = [ + "$SDKROOT/System/Library/Frameworks/UIKit.framework", + "$SDKROOT/System/Library/Frameworks/Foundation.framework", + ], +) + +apple_bundle( + name = "ShareExtension", + binary = ":ShareBinary", + extension = "appex", + info_plist = "Share/Info.plist", + info_plist_substitutions = share_extension_info_plist_substitutions(), + deps = [ + ], + xcode_product_type = "com.apple.product-type.app-extension", +) + +# Widget + +apple_binary( + name = "WidgetBinary", + srcs = glob([ + "Widget/**/*.swift", + ]), + configs = widget_extension_configs(), + swift_compiler_flags = [ + "-application-extension", + ], + linker_flags = [ + "-e", + "_NSExtensionMain", + "-Xlinker", + "-rpath", + "-Xlinker", + "/usr/lib/swift", + "-Xlinker", + "-rpath", + "-Xlinker", + "@executable_path/../../Frameworks", + ], + deps = [ + "//submodules/BuildConfig:BuildConfig", + "//submodules/WidgetItems:WidgetItems", + "//submodules/AppLockState:AppLockState", + ], + frameworks = [ + "$SDKROOT/System/Library/Frameworks/UIKit.framework", + "$SDKROOT/System/Library/Frameworks/Foundation.framework", + "$SDKROOT/System/Library/Frameworks/NotificationCenter.framework", + ], +) + +apple_bundle( + name = "WidgetExtension", + binary = ":WidgetBinary", + extension = "appex", + info_plist = "Widget/Info.plist", + info_plist_substitutions = widget_extension_info_plist_substitutions(), + deps = [ + ], + xcode_product_type = "com.apple.product-type.app-extension", +) + +# Notification Content + +apple_binary( + name = "NotificationContentBinary", + srcs = glob([ + "NotificationContent/**/*.swift", + ]), + configs = notification_content_extension_configs(), + swift_compiler_flags = [ + "-application-extension", + ], + linker_flags = [ + "-e", + "_NSExtensionMain", + "-Xlinker", + "-rpath", + "-Xlinker", + "/usr/lib/swift", + "-Xlinker", + "-rpath", + "-Xlinker", + "@executable_path/../../Frameworks", + ], + deps = [ + "//submodules/TelegramUI:TelegramUI#shared", + ], + frameworks = [ + "$SDKROOT/System/Library/Frameworks/UIKit.framework", + "$SDKROOT/System/Library/Frameworks/Foundation.framework", + "$SDKROOT/System/Library/Frameworks/UserNotificationsUI.framework", + ], +) + +apple_bundle( + name = "NotificationContentExtension", + binary = ":NotificationContentBinary", + extension = "appex", + info_plist = "NotificationContent/Info.plist", + info_plist_substitutions = notification_content_extension_info_plist_substitutions(), + deps = [ + ], + xcode_product_type = "com.apple.product-type.app-extension", +) + +#Notification Service + +apple_binary( + name = "NotificationServiceBinary", + srcs = glob([ + "NotificationService/**/*.m", + "NotificationService/**/*.swift", + ]), + headers = glob([ + "NotificationService/**/*.h", + ]), + bridging_header = "NotificationService/NotificationService-Bridging-Header.h", + configs = notification_service_extension_configs(), + swift_compiler_flags = [ + "-application-extension", + ], + linker_flags = [ + "-e", + "_NSExtensionMain", + "-Xlinker", + "-rpath", + "-Xlinker", + "/usr/lib/swift", + "-Xlinker", + "-rpath", + "-Xlinker", + "@executable_path/../../Frameworks", + ], + deps = [ + "//submodules/BuildConfig:BuildConfig", + "//submodules/MtProtoKit:MtProtoKit#shared", + "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit#shared", + "//submodules/EncryptionProvider:EncryptionProvider", + "//submodules/Database/ValueBox:ValueBox", + "//submodules/Database/PostboxDataTypes:PostboxDataTypes", + "//submodules/Database/MessageHistoryReadStateTable:MessageHistoryReadStateTable", + "//submodules/Database/MessageHistoryMetadataTable:MessageHistoryMetadataTable", + "//submodules/Database/PreferencesTable:PreferencesTable", + "//submodules/Database/PeerTable:PeerTable", + "//submodules/sqlcipher:sqlcipher", + "//submodules/AppLockState:AppLockState", + "//submodules/NotificationsPresentationData:NotificationsPresentationData", + ], + frameworks = [ + "$SDKROOT/System/Library/Frameworks/Foundation.framework", + "$SDKROOT/System/Library/Frameworks/UserNotifications.framework", + ], +) + +apple_bundle( + name = "NotificationServiceExtension", + binary = ":NotificationServiceBinary", + extension = "appex", + info_plist = "NotificationService/Info.plist", + info_plist_substitutions = notification_service_extension_info_plist_substitutions(), + deps = [ + ], + xcode_product_type = "com.apple.product-type.app-extension", +) + +# Intents + +apple_binary( + name = "IntentsBinary", + srcs = glob([ + "SiriIntents/**/*.swift", + ]), + configs = intents_extension_configs(), + swift_compiler_flags = [ + "-application-extension", + ], + linker_flags = [ + "-e", + "_NSExtensionMain", + "-Xlinker", + "-rpath", + "-Xlinker", + "/usr/lib/swift", + "-Xlinker", + "-rpath", + "-Xlinker", + "@executable_path/../../Frameworks", + ], + deps = [ + "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit#shared", + "//submodules/Postbox:Postbox#shared", + "//submodules/TelegramApi:TelegramApi#shared", + "//submodules/SyncCore:SyncCore#shared", + "//submodules/TelegramCore:TelegramCore#shared", + "//submodules/BuildConfig:BuildConfig", + "//submodules/OpenSSLEncryptionProvider:OpenSSLEncryptionProvider", + "//submodules/AppLockState:AppLockState", + ], + frameworks = [ + "$SDKROOT/System/Library/Frameworks/Foundation.framework", + "$SDKROOT/System/Library/Frameworks/Intents.framework", + "$SDKROOT/System/Library/Frameworks/Contacts.framework", + ], +) + +apple_bundle( + name = "IntentsExtension", + binary = ":IntentsBinary", + extension = "appex", + info_plist = "SiriIntents/Info.plist", + info_plist_substitutions = intents_extension_info_plist_substitutions(), + deps = [ + ], + xcode_product_type = "com.apple.product-type.app-extension", +) + +# Watch + +apple_resource( + name = "WatchAppStringResources", + files = [], + variants = glob([ + "Telegram-iOS/*.lproj/Localizable.strings", + ]), + visibility = ["PUBLIC"], +) + +apple_resource( + name = "WatchAppExtensionResources", + files = glob([ + "Watch/Extension/Resources/**/*", + ], exclude = ["Watch/Extension/Resources/**/.*"]), + visibility = ["PUBLIC"], +) + +apple_binary( + name = "WatchAppExtensionBinary", + srcs = glob([ + "Watch/Extension/**/*.m", + "Watch/SSignalKit/**/*.m", + "Watch/Bridge/**/*.m", + "Watch/WatchCommonWatch/**/*.m", + ]), + headers = merge_maps([ + glob_map(glob([ + "Watch/Extension/*.h", + "Watch/Bridge/*.h", + ])), + glob_sub_map("Watch/Extension/", glob([ + "Watch/Extension/SSignalKit/*.h", + ])), + glob_sub_map("Watch/", glob([ + "Watch/WatchCommonWatch/*.h", + ])), + ]), + compiler_flags = [ + "-DTARGET_OS_WATCH=1", + ], + linker_flags = [ + "-e", + "_WKExtensionMain", + "-lWKExtensionMainLegacy", + ], + configs = watch_extension_binary_configs(), + frameworks = [ + "$SDKROOT/System/Library/Frameworks/UserNotifications.framework", + "$SDKROOT/System/Library/Frameworks/CoreLocation.framework", + "$SDKROOT/System/Library/Frameworks/CoreGraphics.framework", + ], + deps = [ + ":WatchAppStringResources", + ":WatchAppExtensionResources", + ], +) + +apple_bundle( + name = "WatchAppExtension", + binary = ":WatchAppExtensionBinary", + extension = "appex", + info_plist = "Watch/Extension/Info.plist", + info_plist_substitutions = watch_extension_info_plist_substitutions(), + xcode_product_type = "com.apple.product-type.watchkit2-extension", +) + +apple_resource( + name = "WatchAppResources", + dirs = [], + files = glob(["Watch/Extension/Resources/*.png"]) +) + +apple_asset_catalog( + name = "WatchAppAssets", + dirs = [ + "Watch/App/Assets.xcassets", + ], + app_icon = "AppIcon", + visibility = ["PUBLIC"], +) + +apple_resource( + name = "WatchAppInterface", + files = [ + "Watch/App/Base.lproj/Interface.storyboard", + ], + visibility = ["PUBLIC"], +) + +apple_binary( + name = "WatchAppBinary", + configs = watch_binary_configs(), + deps = [ + ":WatchAppResources", + ":WatchAppAssets", + ":WatchAppInterface", + ":WatchAppStringResources", + ], +) + +apple_bundle( + name = "WatchApp", + binary = ":WatchAppBinary", + visibility = [ + "//:", + ], + extension = "app", + info_plist = "Watch/App/Info.plist", + info_plist_substitutions = watch_info_plist_substitutions(), + xcode_product_type = "com.apple.product-type.application.watchapp2", + deps = [ + ":WatchAppExtension", + ], +) + +# Package + +apple_package( + name = "AppPackage", + bundle = ":Telegram", +) + +xcode_workspace_config( + name = "workspace", + workspace_name = "Telegram_Buck", + src_target = ":Telegram", +) diff --git a/Telegram/NotificationService/Serialization.m b/Telegram/NotificationService/Serialization.m index f7eea7807e..b14ed21e17 100644 --- a/Telegram/NotificationService/Serialization.m +++ b/Telegram/NotificationService/Serialization.m @@ -3,7 +3,7 @@ @implementation Serialization - (NSUInteger)currentLayer { - return 110; + return 111; } - (id _Nullable)parseMessage:(NSData * _Nullable)data { diff --git a/Telegram/SiriIntents/IntentHandler.swift b/Telegram/SiriIntents/IntentHandler.swift index efc0b4076a..9083112cb9 100644 --- a/Telegram/SiriIntents/IntentHandler.swift +++ b/Telegram/SiriIntents/IntentHandler.swift @@ -7,6 +7,7 @@ import SwiftSignalKit import BuildConfig import Contacts import OpenSSLEncryptionProvider +import AppLockState private var accountCache: Account? @@ -57,6 +58,8 @@ public class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchFo private let resolvePersonsDisposable = MetaDisposable() private let actionDisposable = MetaDisposable() + private var appGroupUrl: URL? + override init() { super.init() @@ -79,6 +82,8 @@ public class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchFo return } + self.appGroupUrl = appGroupUrl + let rootPath = rootPathForBasePath(appGroupUrl.path) performAppGroupUpgrades(appGroupPath: appGroupUrl.path, rootPath: rootPath) @@ -179,6 +184,14 @@ public class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchFo } private func resolve(persons: [INPerson]?, with completion: @escaping ([ResolveResult]) -> Void) { + if let appGroupUrl = self.appGroupUrl { + let rootPath = rootPathForBasePath(appGroupUrl.path) + if let data = try? Data(contentsOf: URL(fileURLWithPath: appLockStatePath(rootPath: rootPath))), let state = try? JSONDecoder().decode(LockState.self, from: data), isAppLocked(state: state) { + completion([.skip]) + return + } + } + let account = self.accountPromise.get() guard let initialPersons = persons, !initialPersons.isEmpty else { completion([.needsValue]) @@ -278,6 +291,14 @@ public class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchFo @available(iOSApplicationExtension 11.0, *) public func resolveRecipients(for intent: INSendMessageIntent, with completion: @escaping ([INSendMessageRecipientResolutionResult]) -> Void) { + if let appGroupUrl = self.appGroupUrl { + let rootPath = rootPathForBasePath(appGroupUrl.path) + if let data = try? Data(contentsOf: URL(fileURLWithPath: appLockStatePath(rootPath: rootPath))), let state = try? JSONDecoder().decode(LockState.self, from: data), isAppLocked(state: state) { + completion([INSendMessageRecipientResolutionResult.notRequired()]) + return + } + } + if let peerId = intent.conversationIdentifier.flatMap(Int64.init) { let account = self.accountPromise.get() @@ -321,6 +342,13 @@ public class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchFo } public func resolveContent(for intent: INSendMessageIntent, with completion: @escaping (INStringResolutionResult) -> Void) { + if let appGroupUrl = self.appGroupUrl { + let rootPath = rootPathForBasePath(appGroupUrl.path) + if let data = try? Data(contentsOf: URL(fileURLWithPath: appLockStatePath(rootPath: rootPath))), let state = try? JSONDecoder().decode(LockState.self, from: data), isAppLocked(state: state) { + completion(INStringResolutionResult.notRequired()) + return + } + } guard CNContactStore.authorizationStatus(for: .contacts) == .authorized else { completion(INStringResolutionResult.notRequired()) return @@ -333,6 +361,15 @@ public class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchFo } public func confirm(intent: INSendMessageIntent, completion: @escaping (INSendMessageIntentResponse) -> Void) { + if let appGroupUrl = self.appGroupUrl { + let rootPath = rootPathForBasePath(appGroupUrl.path) + if let data = try? Data(contentsOf: URL(fileURLWithPath: appLockStatePath(rootPath: rootPath))), let state = try? JSONDecoder().decode(LockState.self, from: data), isAppLocked(state: state) { + let userActivity = NSUserActivity(activityType: NSStringFromClass(INSendMessageIntent.self)) + let response = INSendMessageIntentResponse(code: .failureRequiringAppLaunch, userActivity: userActivity) + completion(response) + return + } + } let userActivity = NSUserActivity(activityType: NSStringFromClass(INSendMessageIntent.self)) guard CNContactStore.authorizationStatus(for: .contacts) == .authorized else { let response = INSendMessageIntentResponse(code: .failureRequiringAppLaunch, userActivity: userActivity) @@ -344,6 +381,16 @@ public class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchFo } public func handle(intent: INSendMessageIntent, completion: @escaping (INSendMessageIntentResponse) -> Void) { + if let appGroupUrl = self.appGroupUrl { + let rootPath = rootPathForBasePath(appGroupUrl.path) + if let data = try? Data(contentsOf: URL(fileURLWithPath: appLockStatePath(rootPath: rootPath))), let state = try? JSONDecoder().decode(LockState.self, from: data), isAppLocked(state: state) { + let userActivity = NSUserActivity(activityType: NSStringFromClass(INSendMessageIntent.self)) + let response = INSendMessageIntentResponse(code: .failureRequiringAppLaunch, userActivity: userActivity) + completion(response) + return + } + } + self.actionDisposable.set((self.accountPromise.get() |> castError(IntentHandlingError.self) |> take(1) @@ -394,6 +441,16 @@ public class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchFo } public func handle(intent: INSearchForMessagesIntent, completion: @escaping (INSearchForMessagesIntentResponse) -> Void) { + if let appGroupUrl = self.appGroupUrl { + let rootPath = rootPathForBasePath(appGroupUrl.path) + if let data = try? Data(contentsOf: URL(fileURLWithPath: appLockStatePath(rootPath: rootPath))), let state = try? JSONDecoder().decode(LockState.self, from: data), isAppLocked(state: state) { + let userActivity = NSUserActivity(activityType: NSStringFromClass(INSearchForMessagesIntent.self)) + let response = INSearchForMessagesIntentResponse(code: .failureRequiringAppLaunch, userActivity: userActivity) + completion(response) + return + } + } + self.actionDisposable.set((self.accountPromise.get() |> take(1) |> castError(IntentHandlingError.self) @@ -455,6 +512,16 @@ public class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchFo } public func handle(intent: INSetMessageAttributeIntent, completion: @escaping (INSetMessageAttributeIntentResponse) -> Void) { + if let appGroupUrl = self.appGroupUrl { + let rootPath = rootPathForBasePath(appGroupUrl.path) + if let data = try? Data(contentsOf: URL(fileURLWithPath: appLockStatePath(rootPath: rootPath))), let state = try? JSONDecoder().decode(LockState.self, from: data), isAppLocked(state: state) { + let userActivity = NSUserActivity(activityType: NSStringFromClass(INSetMessageAttributeIntent.self)) + let response = INSetMessageAttributeIntentResponse(code: .failure, userActivity: userActivity) + completion(response) + return + } + } + self.actionDisposable.set((self.accountPromise.get() |> castError(IntentHandlingError.self) |> take(1) @@ -514,6 +581,14 @@ public class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchFo // MARK: - INStartAudioCallIntentHandling public func resolveContacts(for intent: INStartAudioCallIntent, with completion: @escaping ([INPersonResolutionResult]) -> Void) { + if let appGroupUrl = self.appGroupUrl { + let rootPath = rootPathForBasePath(appGroupUrl.path) + if let data = try? Data(contentsOf: URL(fileURLWithPath: appLockStatePath(rootPath: rootPath))), let state = try? JSONDecoder().decode(LockState.self, from: data), isAppLocked(state: state) { + completion([INPersonResolutionResult.notRequired()]) + return + } + } + guard CNContactStore.authorizationStatus(for: .contacts) == .authorized else { completion([INPersonResolutionResult.notRequired()]) return @@ -529,6 +604,16 @@ public class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchFo } public func handle(intent: INStartAudioCallIntent, completion: @escaping (INStartAudioCallIntentResponse) -> Void) { + if let appGroupUrl = self.appGroupUrl { + let rootPath = rootPathForBasePath(appGroupUrl.path) + if let data = try? Data(contentsOf: URL(fileURLWithPath: appLockStatePath(rootPath: rootPath))), let state = try? JSONDecoder().decode(LockState.self, from: data), isAppLocked(state: state) { + let userActivity = NSUserActivity(activityType: NSStringFromClass(INStartAudioCallIntent.self)) + let response = INStartAudioCallIntentResponse(code: .failureRequiringAppLaunch, userActivity: userActivity) + completion(response) + return + } + } + self.actionDisposable.set((self.accountPromise.get() |> castError(IntentHandlingError.self) |> take(1) @@ -572,6 +657,16 @@ public class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchFo }*/ public func handle(intent: INSearchCallHistoryIntent, completion: @escaping (INSearchCallHistoryIntentResponse) -> Void) { + if let appGroupUrl = self.appGroupUrl { + let rootPath = rootPathForBasePath(appGroupUrl.path) + if let data = try? Data(contentsOf: URL(fileURLWithPath: appLockStatePath(rootPath: rootPath))), let state = try? JSONDecoder().decode(LockState.self, from: data), isAppLocked(state: state) { + let userActivity = NSUserActivity(activityType: NSStringFromClass(INSearchCallHistoryIntent.self)) + let response = INSearchCallHistoryIntentResponse(code: .failureRequiringAppLaunch, userActivity: userActivity) + completion(response) + return + } + } + self.actionDisposable.set((self.accountPromise.get() |> take(1) |> castError(IntentHandlingError.self) diff --git a/Telegram/Telegram-iOS/Telegram-iOS-Hockeyapp.entitlements b/Telegram/Telegram-iOS/Telegram-iOS-Hockeyapp.entitlements index f4771f658b..97391bb537 100644 --- a/Telegram/Telegram-iOS/Telegram-iOS-Hockeyapp.entitlements +++ b/Telegram/Telegram-iOS/Telegram-iOS-Hockeyapp.entitlements @@ -34,7 +34,5 @@ com.apple.developer.pushkit.unrestricted-voip - application-identifier - X834Q8SBVP.org.telegram.Telegram-iOS diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 061741053b..a0af4e214f 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -5344,3 +5344,25 @@ Any member of this group will be able to see messages in the channel."; "PeerInfo.AddToContacts" = "Add to Contacts"; "PeerInfo.BioExpand" = "more"; + +"External.OpenIn" = "Open in %@"; + +"ChatList.EmptyChatList" = "You have no\nconversations yet."; +"ChatList.EmptyChatFilterList" = "No chats currently\nmatch this filter."; + +"ChatList.EmptyChatListNewMessage" = "New Message"; +"ChatList.EmptyChatListEditFilter" = "Edit Filter"; + +"Stats.Overview" = "OVERVIEW"; +"Stats.Followers" = "Followers"; +"Stats.EnabledNotifications" = "Enabled Notifications"; +"Stats.ViewsPerPost" = "Views Per Post"; +"Stats.SharesPerPost" = "Shares Per Post"; + +"Stats.GrowthTitle" = "GROWTH"; +"Stats.FollowersTitle" = "FOLLOWERS"; +"Stats.NotificationsTitle" = "NOTIFICATIONS"; +"Stats.InteractionsTitle" = "INTERACTIONS"; +"Stats.ViewsBySourceTitle" = "VIEWS BY SOURCE"; +"Stats.FollowersBySourceTitle" = "FOLLOWERS BY SOURCE"; +"Stats.LanguagesTitle" = "LANGUAGES"; diff --git a/build-system/verify.sh b/build-system/verify.sh index 89e33bef02..d260604e30 100644 --- a/build-system/verify.sh +++ b/build-system/verify.sh @@ -21,22 +21,22 @@ if [ -z "$BUILD_NUMBER" ]; then exit 1 fi -export ENTITLEMENTS_APP="Telegram-iOS/Telegram-iOS-AppStoreLLC.entitlements" +export ENTITLEMENTS_APP="Telegram/Telegram-iOS/Telegram-iOS-AppStoreLLC.entitlements" export DEVELOPMENT_PROVISIONING_PROFILE_APP="match Development ph.telegra.Telegraph" export DISTRIBUTION_PROVISIONING_PROFILE_APP="match AppStore ph.telegra.Telegraph" -export ENTITLEMENTS_EXTENSION_SHARE="Share/Share-AppStoreLLC.entitlements" +export ENTITLEMENTS_EXTENSION_SHARE="Telegram/Share/Share-AppStoreLLC.entitlements" export DEVELOPMENT_PROVISIONING_PROFILE_EXTENSION_SHARE="match Development ph.telegra.Telegraph.Share" export DISTRIBUTION_PROVISIONING_PROFILE_EXTENSION_SHARE="match AppStore ph.telegra.Telegraph.Share" -export ENTITLEMENTS_EXTENSION_WIDGET="Widget/Widget-AppStoreLLC.entitlements" +export ENTITLEMENTS_EXTENSION_WIDGET="Telegram/Widget/Widget-AppStoreLLC.entitlements" export DEVELOPMENT_PROVISIONING_PROFILE_EXTENSION_WIDGET="match Development ph.telegra.Telegraph.Widget" export DISTRIBUTION_PROVISIONING_PROFILE_EXTENSION_WIDGET="match AppStore ph.telegra.Telegraph.Widget" -export ENTITLEMENTS_EXTENSION_NOTIFICATIONSERVICE="NotificationService/NotificationService-AppStoreLLC.entitlements" +export ENTITLEMENTS_EXTENSION_NOTIFICATIONSERVICE="Telegram/NotificationService/NotificationService-AppStoreLLC.entitlements" export DEVELOPMENT_PROVISIONING_PROFILE_EXTENSION_NOTIFICATIONSERVICE="match Development ph.telegra.Telegraph.NotificationService" export DISTRIBUTION_PROVISIONING_PROFILE_EXTENSION_NOTIFICATIONSERVICE="match AppStore ph.telegra.Telegraph.NotificationService" -export ENTITLEMENTS_EXTENSION_NOTIFICATIONCONTENT="NotificationContent/NotificationContent-AppStoreLLC.entitlements" +export ENTITLEMENTS_EXTENSION_NOTIFICATIONCONTENT="Telegram/NotificationContent/NotificationContent-AppStoreLLC.entitlements" export DEVELOPMENT_PROVISIONING_PROFILE_EXTENSION_NOTIFICATIONCONTENT="match Development ph.telegra.Telegraph.NotificationContent" export DISTRIBUTION_PROVISIONING_PROFILE_EXTENSION_NOTIFICATIONCONTENT="match AppStore ph.telegra.Telegraph.NotificationContent" -export ENTITLEMENTS_EXTENSION_INTENTS="SiriIntents/SiriIntents-AppStoreLLC.entitlements" +export ENTITLEMENTS_EXTENSION_INTENTS="Telegram/SiriIntents/SiriIntents-AppStoreLLC.entitlements" export DEVELOPMENT_PROVISIONING_PROFILE_EXTENSION_INTENTS="match Development ph.telegra.Telegraph.SiriIntents" export DISTRIBUTION_PROVISIONING_PROFILE_EXTENSION_INTENTS="match AppStore ph.telegra.Telegraph.SiriIntents" export DEVELOPMENT_PROVISIONING_PROFILE_WATCH_APP="match Development ph.telegra.Telegraph.watchkitapp" diff --git a/submodules/AccountContext/Sources/ContactMultiselectionController.swift b/submodules/AccountContext/Sources/ContactMultiselectionController.swift index 23ff45ff11..0d69cfc183 100644 --- a/submodules/AccountContext/Sources/ContactMultiselectionController.swift +++ b/submodules/AccountContext/Sources/ContactMultiselectionController.swift @@ -5,7 +5,7 @@ import Postbox public enum ContactMultiselectionControllerMode { case groupCreation - case peerSelection(searchChatList: Bool, searchGroups: Bool) + case peerSelection(searchChatList: Bool, searchGroups: Bool, searchChannels: Bool) case channelCreation } diff --git a/submodules/AsyncDisplayKit/Source/ASAbstractLayoutController+FrameworkPrivate.h b/submodules/AsyncDisplayKit/Source/ASAbstractLayoutController+FrameworkPrivate.h deleted file mode 100644 index f836fa0b84..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASAbstractLayoutController+FrameworkPrivate.h +++ /dev/null @@ -1,21 +0,0 @@ -// -// ASAbstractLayoutController+FrameworkPrivate.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -// -// The following methods are ONLY for use by _ASDisplayLayer, _ASDisplayView, and ASDisplayNode. -// These methods must never be called or overridden by other classes. -// - -#include - -@interface ASAbstractLayoutController (FrameworkPrivate) - -+ (std::vector>)defaultTuningParameters; - -@end diff --git a/submodules/AsyncDisplayKit/Source/ASAsciiArtBoxCreator.mm b/submodules/AsyncDisplayKit/Source/ASAsciiArtBoxCreator.mm deleted file mode 100644 index 78eb572ead..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASAsciiArtBoxCreator.mm +++ /dev/null @@ -1,186 +0,0 @@ -// -// ASAsciiArtBoxCreator.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#import -#import - -static const NSUInteger kDebugBoxPadding = 2; - -typedef NS_ENUM(NSUInteger, PIDebugBoxPaddingLocation) -{ - PIDebugBoxPaddingLocationFront, - PIDebugBoxPaddingLocationEnd, - PIDebugBoxPaddingLocationBoth -}; - -@interface NSString(PIDebugBox) - -@end - -@implementation NSString(PIDebugBox) - -+ (instancetype)debugbox_stringWithString:(NSString *)stringToRepeat repeatedCount:(NSUInteger)repeatCount NS_RETURNS_RETAINED -{ - NSMutableString *string = [[NSMutableString alloc] initWithCapacity:[stringToRepeat length] * repeatCount]; - for (NSUInteger index = 0; index < repeatCount; index++) { - [string appendString:stringToRepeat]; - } - return [string copy]; -} - -- (NSString *)debugbox_stringByAddingPadding:(NSString *)padding count:(NSUInteger)count location:(PIDebugBoxPaddingLocation)location -{ - NSString *paddingString = [NSString debugbox_stringWithString:padding repeatedCount:count]; - switch (location) { - case PIDebugBoxPaddingLocationFront: - return [NSString stringWithFormat:@"%@%@", paddingString, self]; - case PIDebugBoxPaddingLocationEnd: - return [NSString stringWithFormat:@"%@%@", self, paddingString]; - case PIDebugBoxPaddingLocationBoth: - return [NSString stringWithFormat:@"%@%@%@", paddingString, self, paddingString]; - } - return [self copy]; -} - -@end - -@implementation ASAsciiArtBoxCreator - -+ (NSString *)horizontalBoxStringForChildren:(NSArray *)children parent:(NSString *)parent -{ - if ([children count] == 0) { - return parent; - } - - NSMutableArray *childrenLines = [NSMutableArray array]; - - // split the children into lines - NSUInteger lineCountPerChild = 0; - for (NSString *child in children) { - NSArray *lines = [child componentsSeparatedByString:@"\n"]; - lineCountPerChild = MAX(lineCountPerChild, [lines count]); - } - - for (NSString *child in children) { - NSMutableArray *lines = [[child componentsSeparatedByString:@"\n"] mutableCopy]; - NSUInteger topPadding = ceil((CGFloat)(lineCountPerChild - [lines count])/2.0); - NSUInteger bottomPadding = (lineCountPerChild - [lines count])/2.0; - NSUInteger lineLength = [lines[0] length]; - - for (NSUInteger index = 0; index < topPadding; index++) { - [lines insertObject:[NSString debugbox_stringWithString:@" " repeatedCount:lineLength] atIndex:0]; - } - for (NSUInteger index = 0; index < bottomPadding; index++) { - [lines addObject:[NSString debugbox_stringWithString:@" " repeatedCount:lineLength]]; - } - [childrenLines addObject:lines]; - } - - NSMutableArray *concatenatedLines = [NSMutableArray array]; - NSString *padding = [NSString debugbox_stringWithString:@" " repeatedCount:kDebugBoxPadding]; - for (NSUInteger index = 0; index < lineCountPerChild; index++) { - NSMutableString *line = [[NSMutableString alloc] init]; - [line appendFormat:@"|%@",padding]; - for (NSArray *childLines in childrenLines) { - [line appendFormat:@"%@%@", childLines[index], padding]; - } - [line appendString:@"|"]; - [concatenatedLines addObject:line]; - } - - // surround the lines in a box - NSUInteger totalLineLength = [concatenatedLines[0] length]; - if (totalLineLength < [parent length]) { - NSUInteger difference = [parent length] + (2 * kDebugBoxPadding) - totalLineLength; - NSUInteger leftPadding = ceil((CGFloat)difference/2.0); - NSUInteger rightPadding = difference/2; - - NSString *leftString = [@"|" debugbox_stringByAddingPadding:@" " count:leftPadding location:PIDebugBoxPaddingLocationEnd]; - NSString *rightString = [@"|" debugbox_stringByAddingPadding:@" " count:rightPadding location:PIDebugBoxPaddingLocationFront]; - - NSMutableArray *paddedLines = [NSMutableArray array]; - for (NSString *line in concatenatedLines) { - NSString *paddedLine = [line stringByReplacingOccurrencesOfString:@"|" withString:leftString options:NSCaseInsensitiveSearch range:NSMakeRange(0, 1)]; - paddedLine = [paddedLine stringByReplacingOccurrencesOfString:@"|" withString:rightString options:NSCaseInsensitiveSearch range:NSMakeRange([paddedLine length] - 1, 1)]; - [paddedLines addObject:paddedLine]; - } - concatenatedLines = paddedLines; - // totalLineLength += difference; - } - concatenatedLines = [self appendTopAndBottomToBoxString:concatenatedLines parent:parent]; - return [concatenatedLines componentsJoinedByString:@"\n"]; - -} - -+ (NSString *)verticalBoxStringForChildren:(NSArray *)children parent:(NSString *)parent -{ - if ([children count] == 0) { - return parent; - } - - NSMutableArray *childrenLines = [NSMutableArray array]; - - NSUInteger maxChildLength = 0; - for (NSString *child in children) { - NSArray *lines = [child componentsSeparatedByString:@"\n"]; - maxChildLength = MAX(maxChildLength, [lines[0] length]); - } - - NSUInteger rightPadding = 0; - NSUInteger leftPadding = 0; - - if (maxChildLength < [parent length]) { - NSUInteger difference = [parent length] + (2 * kDebugBoxPadding) - maxChildLength; - leftPadding = ceil((CGFloat)difference/2.0); - rightPadding = difference/2; - } - - NSString *rightPaddingString = [NSString debugbox_stringWithString:@" " repeatedCount:rightPadding + kDebugBoxPadding]; - NSString *leftPaddingString = [NSString debugbox_stringWithString:@" " repeatedCount:leftPadding + kDebugBoxPadding]; - - for (NSString *child in children) { - NSMutableArray *lines = [[child componentsSeparatedByString:@"\n"] mutableCopy]; - - NSUInteger leftLinePadding = ceil((CGFloat)(maxChildLength - [lines[0] length])/2.0); - NSUInteger rightLinePadding = (maxChildLength - [lines[0] length])/2.0; - - for (NSString *line in lines) { - NSString *rightLinePaddingString = [NSString debugbox_stringWithString:@" " repeatedCount:rightLinePadding]; - rightLinePaddingString = [NSString stringWithFormat:@"%@%@|", rightLinePaddingString, rightPaddingString]; - - NSString *leftLinePaddingString = [NSString debugbox_stringWithString:@" " repeatedCount:leftLinePadding]; - leftLinePaddingString = [NSString stringWithFormat:@"|%@%@", leftLinePaddingString, leftPaddingString]; - - NSString *paddingLine = [NSString stringWithFormat:@"%@%@%@", leftLinePaddingString, line, rightLinePaddingString]; - [childrenLines addObject:paddingLine]; - } - } - - childrenLines = [self appendTopAndBottomToBoxString:childrenLines parent:parent]; - return [childrenLines componentsJoinedByString:@"\n"]; -} - -+ (NSMutableArray *)appendTopAndBottomToBoxString:(NSMutableArray *)boxStrings parent:(NSString *)parent -{ - NSUInteger totalLineLength = [boxStrings[0] length]; - [boxStrings addObject:[NSString debugbox_stringWithString:@"-" repeatedCount:totalLineLength]]; - - NSUInteger leftPadding = ceil(((CGFloat)(totalLineLength - [parent length]))/2.0); - NSUInteger rightPadding = (totalLineLength - [parent length])/2; - - NSString *topLine = [parent debugbox_stringByAddingPadding:@"-" count:leftPadding location:PIDebugBoxPaddingLocationFront]; - topLine = [topLine debugbox_stringByAddingPadding:@"-" count:rightPadding location:PIDebugBoxPaddingLocationEnd]; - [boxStrings insertObject:topLine atIndex:0]; - - return boxStrings; -} - -@end diff --git a/submodules/AsyncDisplayKit/Source/ASAssert.mm b/submodules/AsyncDisplayKit/Source/ASAssert.mm deleted file mode 100644 index 6a78b06eaf..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASAssert.mm +++ /dev/null @@ -1,58 +0,0 @@ -// -// ASAssert.mm -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import - -#if AS_TLS_AVAILABLE - -static _Thread_local int tls_mainThreadAssertionsDisabledCount; -BOOL ASMainThreadAssertionsAreDisabled() { - return tls_mainThreadAssertionsDisabledCount > 0; -} - -void ASPushMainThreadAssertionsDisabled() { - tls_mainThreadAssertionsDisabledCount += 1; -} - -void ASPopMainThreadAssertionsDisabled() { - tls_mainThreadAssertionsDisabledCount -= 1; - ASDisplayNodeCAssert(tls_mainThreadAssertionsDisabledCount >= 0, @"Attempt to pop thread assertion-disabling without corresponding push."); -} - -#else - -#import - -static pthread_key_t ASMainThreadAssertionsDisabledKey() { - static pthread_key_t k; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - pthread_key_create(&k, NULL); - }); - return k; -} - -BOOL ASMainThreadAssertionsAreDisabled() { - return (nullptr != pthread_getspecific(ASMainThreadAssertionsDisabledKey())); -} - -void ASPushMainThreadAssertionsDisabled() { - const auto key = ASMainThreadAssertionsDisabledKey(); - const auto oldVal = (intptr_t)pthread_getspecific(key); - pthread_setspecific(key, (void *)(oldVal + 1)); -} - -void ASPopMainThreadAssertionsDisabled() { - const auto key = ASMainThreadAssertionsDisabledKey(); - const auto oldVal = (intptr_t)pthread_getspecific(key); - pthread_setspecific(key, (void *)(oldVal - 1)); - ASDisplayNodeCAssert(oldVal > 0, @"Attempt to pop thread assertion-disabling without corresponding push."); -} - -#endif // AS_TLS_AVAILABLE diff --git a/submodules/AsyncDisplayKit/Source/ASCGImageBuffer.h b/submodules/AsyncDisplayKit/Source/ASCGImageBuffer.h deleted file mode 100644 index a77452622b..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASCGImageBuffer.h +++ /dev/null @@ -1,28 +0,0 @@ -// -// ASCGImageBuffer.h -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -AS_SUBCLASSING_RESTRICTED -@interface ASCGImageBuffer : NSObject - -/// Init a zero-filled buffer with the given length. -- (instancetype)initWithLength:(NSUInteger)length; - -@property (readonly) void *mutableBytes NS_RETURNS_INNER_POINTER; - -/// Don't do any drawing or call any methods after calling this. -- (CGDataProviderRef)createDataProviderAndInvalidate CF_RETURNS_RETAINED; - -@end - -NS_ASSUME_NONNULL_END diff --git a/submodules/AsyncDisplayKit/Source/ASCGImageBuffer.mm b/submodules/AsyncDisplayKit/Source/ASCGImageBuffer.mm deleted file mode 100644 index 6f05300e23..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASCGImageBuffer.mm +++ /dev/null @@ -1,88 +0,0 @@ -// -// ASCGImageBuffer.mm -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import "ASCGImageBuffer.h" - -#import -#import -#import -#import - -/** - * The behavior of this class is modeled on the private function - * _CGDataProviderCreateWithCopyOfData, which is the function used - * by CGBitmapContextCreateImage. - * - * If the buffer is larger than a page, we use mmap and mark it as - * read-only when they are finished drawing. Then we wrap the VM - * in an NSData - */ -@implementation ASCGImageBuffer { - BOOL _createdData; - BOOL _isVM; - NSUInteger _length; -} - -- (instancetype)initWithLength:(NSUInteger)length -{ - if (self = [super init]) { - _length = length; - _isVM = (length >= vm_page_size); - if (_isVM) { - _mutableBytes = mmap(NULL, length, PROT_WRITE | PROT_READ, MAP_ANONYMOUS | MAP_PRIVATE, VM_MAKE_TAG(VM_MEMORY_COREGRAPHICS_DATA), 0); - if (_mutableBytes == MAP_FAILED) { - NSAssert(NO, @"Failed to map for CG image data."); - _isVM = NO; - } - } - - // Check the VM flag again because we may have failed above. - if (!_isVM) { - _mutableBytes = calloc(1, length); - } - } - return self; -} - -- (void)dealloc -{ - if (!_createdData) { - [ASCGImageBuffer deallocateBuffer:_mutableBytes length:_length isVM:_isVM]; - } -} - -- (CGDataProviderRef)createDataProviderAndInvalidate -{ - NSAssert(!_createdData, @"Should not create data provider from buffer multiple times."); - _createdData = YES; - - // Mark the pages as read-only. - if (_isVM) { - __unused kern_return_t result = vm_protect(mach_task_self(), (vm_address_t)_mutableBytes, _length, true, VM_PROT_READ); - NSAssert(result == noErr, @"Error marking buffer as read-only: %@", [NSError errorWithDomain:NSMachErrorDomain code:result userInfo:nil]); - } - - // Wrap in an NSData - BOOL isVM = _isVM; - NSData *d = [[NSData alloc] initWithBytesNoCopy:_mutableBytes length:_length deallocator:^(void * _Nonnull bytes, NSUInteger length) { - [ASCGImageBuffer deallocateBuffer:bytes length:length isVM:isVM]; - }]; - return CGDataProviderCreateWithCFData((__bridge CFDataRef)d); -} - -+ (void)deallocateBuffer:(void *)buf length:(NSUInteger)length isVM:(BOOL)isVM -{ - if (isVM) { - __unused kern_return_t result = vm_deallocate(mach_task_self(), (vm_address_t)buf, length); - NSAssert(result == noErr, @"Failed to unmap cg image buffer: %@", [NSError errorWithDomain:NSMachErrorDomain code:result userInfo:nil]); - } else { - free(buf); - } -} - -@end diff --git a/submodules/AsyncDisplayKit/Source/ASCollections.mm b/submodules/AsyncDisplayKit/Source/ASCollections.mm deleted file mode 100644 index 592dee2e88..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASCollections.mm +++ /dev/null @@ -1,61 +0,0 @@ -// -// ASCollections.mm -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -/** - * A private allocator that signals to our retain callback to skip the retain. - * It behaves the same as the default allocator, but acts as a signal that we - * are creating a transfer array so we should skip the retain. - */ -static CFAllocatorRef gTransferAllocator; - -static const void *ASTransferRetain(CFAllocatorRef allocator, const void *val) { - if (allocator == gTransferAllocator) { - // Transfer allocator. Ignore retain and pass through. - return val; - } else { - // Other allocator. Retain like normal. - // This happens when they make a mutable copy. - return (&kCFTypeArrayCallBacks)->retain(allocator, val); - } -} - -@implementation NSArray (ASCollections) - -+ (NSArray *)arrayByTransferring:(__strong id *)pointers count:(NSUInteger)count NS_RETURNS_RETAINED -{ - // Custom callbacks that point to our ASTransferRetain callback. - static CFArrayCallBacks callbacks; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - callbacks = kCFTypeArrayCallBacks; - callbacks.retain = ASTransferRetain; - CFAllocatorContext ctx; - CFAllocatorGetContext(NULL, &ctx); - gTransferAllocator = CFAllocatorCreate(NULL, &ctx); - }); - - // NSZeroArray fast path. - if (count == 0) { - return @[]; // Does not actually call +array when optimized. - } - - // NSSingleObjectArray fast path. Retain/release here is worth it. - if (count == 1) { - NSArray *result = [[NSArray alloc] initWithObjects:pointers count:1]; - pointers[0] = nil; - return result; - } - - NSArray *result = (__bridge_transfer NSArray *)CFArrayCreate(gTransferAllocator, (const void **)(void *)pointers, count, &callbacks); - memset(pointers, 0, count * sizeof(id)); - return result; -} - -@end diff --git a/submodules/AsyncDisplayKit/Source/ASConfiguration.mm b/submodules/AsyncDisplayKit/Source/ASConfiguration.mm deleted file mode 100644 index 34f11bd366..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASConfiguration.mm +++ /dev/null @@ -1,64 +0,0 @@ -// -// ASConfiguration.mm -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import - -/// Not too performance-sensitive here. - -@implementation ASConfiguration - -- (instancetype)initWithDictionary:(NSDictionary *)dictionary -{ - if (self = [super init]) { - if (dictionary != nil) { - const auto featureStrings = ASDynamicCast(dictionary[@"experimental_features"], NSArray); - const auto version = ASDynamicCast(dictionary[@"version"], NSNumber).integerValue; - if (version != ASConfigurationSchemaCurrentVersion) { - NSLog(@"Texture warning: configuration schema is old version (%ld vs %ld)", (long)version, (long)ASConfigurationSchemaCurrentVersion); - } - self.experimentalFeatures = ASExperimentalFeaturesFromArray(featureStrings); - } else { - self.experimentalFeatures = kNilOptions; - } - } - return self; -} - -- (id)copyWithZone:(NSZone *)zone -{ - ASConfiguration *config = [[ASConfiguration alloc] initWithDictionary:nil]; - config.experimentalFeatures = self.experimentalFeatures; - config.delegate = self.delegate; - return config; -} - -@end - -//#define AS_FIXED_CONFIG_JSON "{ \"version\" : 1, \"experimental_features\": [ \"exp_text_node\" ] }" - -#ifdef AS_FIXED_CONFIG_JSON - -@implementation ASConfiguration (UserProvided) - -+ (ASConfiguration *)textureConfiguration NS_RETURNS_RETAINED -{ - NSData *data = [@AS_FIXED_CONFIG_JSON dataUsingEncoding:NSUTF8StringEncoding]; - NSError *error; - NSDictionary *d = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error]; - if (!d) { - NSAssert(NO, @"Error parsing fixed config string '%s': %@", AS_FIXED_CONFIG_JSON, error); - return nil; - } else { - return [[ASConfiguration alloc] initWithDictionary:d]; - } -} - -@end - -#endif // AS_FIXED_CONFIG_JSON diff --git a/submodules/AsyncDisplayKit/Source/ASConfigurationInternal.mm b/submodules/AsyncDisplayKit/Source/ASConfigurationInternal.mm deleted file mode 100644 index 2fb190103a..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASConfigurationInternal.mm +++ /dev/null @@ -1,111 +0,0 @@ -// -// ASConfigurationInternal.mm -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import -#import -#import -#import - -static ASConfigurationManager *ASSharedConfigurationManager; -static dispatch_once_t ASSharedConfigurationManagerOnceToken; - -NS_INLINE ASConfigurationManager *ASConfigurationManagerGet() { - dispatch_once(&ASSharedConfigurationManagerOnceToken, ^{ - ASSharedConfigurationManager = [[ASConfigurationManager alloc] init]; - }); - return ASSharedConfigurationManager; -} - -@implementation ASConfigurationManager { - ASConfiguration *_config; - dispatch_queue_t _delegateQueue; - BOOL _frameworkInitialized; - _Atomic(ASExperimentalFeatures) _activatedExperiments; -} - -+ (ASConfiguration *)defaultConfiguration NS_RETURNS_RETAINED -{ - ASConfiguration *config = [[ASConfiguration alloc] init]; - // TODO(wsdwsd0829): Fix #788 before enabling it. - // config.experimentalFeatures = ASExperimentalInterfaceStateCoalescing; - return config; -} - -- (instancetype)init -{ - if (self = [super init]) { - _delegateQueue = dispatch_queue_create("org.TextureGroup.Texture.ConfigNotifyQueue", DISPATCH_QUEUE_SERIAL); - if ([ASConfiguration respondsToSelector:@selector(textureConfiguration)]) { - _config = [[ASConfiguration textureConfiguration] copy]; - } else { - _config = [ASConfigurationManager defaultConfiguration]; - } - } - return self; -} - -- (void)frameworkDidInitialize -{ - ASDisplayNodeAssertMainThread(); - if (_frameworkInitialized) { - ASDisplayNodeFailAssert(@"Framework initialized twice."); - return; - } - _frameworkInitialized = YES; - - const auto delegate = _config.delegate; - if ([delegate respondsToSelector:@selector(textureDidInitialize)]) { - [delegate textureDidInitialize]; - } -} - -- (BOOL)activateExperimentalFeature:(ASExperimentalFeatures)requested -{ - if (_config == nil) { - return NO; - } - - NSAssert(__builtin_popcountl(requested) == 1, @"Cannot activate multiple features at once with this method."); - - // We need to call out, whether it's enabled or not. - // A/B testing requires even "control" users to be activated. - ASExperimentalFeatures enabled = requested & _config.experimentalFeatures; - ASExperimentalFeatures prevTriggered = atomic_fetch_or(&_activatedExperiments, requested); - ASExperimentalFeatures newlyTriggered = requested & ~prevTriggered; - - // Notify delegate if needed. - if (newlyTriggered != 0) { - __unsafe_unretained id del = _config.delegate; - dispatch_async(_delegateQueue, ^{ - [del textureDidActivateExperimentalFeatures:newlyTriggered]; - }); - } - - return (enabled != 0); -} - -// Define this even when !DEBUG, since we may run our tests in release mode. -+ (void)test_resetWithConfiguration:(ASConfiguration *)configuration -{ - ASConfigurationManager *inst = ASConfigurationManagerGet(); - inst->_config = configuration ?: [self defaultConfiguration]; - atomic_store(&inst->_activatedExperiments, 0); -} - -@end - -BOOL _ASActivateExperimentalFeature(ASExperimentalFeatures feature) -{ - return [ASConfigurationManagerGet() activateExperimentalFeature:feature]; -} - -void ASNotifyInitialized() -{ - [ASConfigurationManagerGet() frameworkDidInitialize]; -} diff --git a/submodules/AsyncDisplayKit/Source/ASControlNode+Private.h b/submodules/AsyncDisplayKit/Source/ASControlNode+Private.h deleted file mode 100644 index 02f54a20ec..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASControlNode+Private.h +++ /dev/null @@ -1,18 +0,0 @@ -// -// ASControlNode+Private.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -@interface ASControlNode (Private) - -#if TARGET_OS_TV -- (void)_pressDown; -#endif - -@end diff --git a/submodules/AsyncDisplayKit/Source/ASControlNode.mm b/submodules/AsyncDisplayKit/Source/ASControlNode.mm deleted file mode 100644 index dcdb9ec829..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASControlNode.mm +++ /dev/null @@ -1,499 +0,0 @@ -// -// ASControlNode.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import "ASControlNode+Private.h" -#import -#import -#import -#import -#import -#import - -// UIControl allows dragging some distance outside of the control itself during -// tracking. This value depends on the device idiom (25 or 70 points), so -// so replicate that effect with the same values here for our own controls. -#define kASControlNodeExpandedInset (([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) ? -25.0f : -70.0f) - -// Initial capacities for dispatch tables. -#define kASControlNodeEventDispatchTableInitialCapacity 4 -#define kASControlNodeActionDispatchTableInitialCapacity 4 - -@interface ASControlNode () -{ -@private - // Control Attributes - BOOL _enabled; - BOOL _highlighted; - - // Tracking - BOOL _tracking; - BOOL _touchInside; - - // Target action pairs stored in an array for each event type - // ASControlEvent -> [ASTargetAction0, ASTargetAction1] - NSMutableDictionary, NSMutableArray *> *_controlEventDispatchTable; -} - -// Read-write overrides. -@property (getter=isTracking) BOOL tracking; -@property (getter=isTouchInside) BOOL touchInside; - -/** - @abstract Returns a key to be used in _controlEventDispatchTable that identifies the control event. - @param controlEvent A control event. - @result A key for use in _controlEventDispatchTable. - */ -id _ASControlNodeEventKeyForControlEvent(ASControlNodeEvent controlEvent); - -/** - @abstract Enumerates the ASControlNode events included mask, invoking the block for each event. - @param mask An ASControlNodeEvent mask. - @param block The block to be invoked for each ASControlNodeEvent included in mask. - */ -void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, void (^block)(ASControlNodeEvent anEvent)); - -/** - @abstract Returns the expanded bounds used to determine if a touch is considered 'inside' during tracking. - @param controlNode A control node. - @result The expanded bounds of the node. - */ -CGRect _ASControlNodeGetExpandedBounds(ASControlNode *controlNode); - - -@end - -@implementation ASControlNode -{ -} - -#pragma mark - Lifecycle - -- (instancetype)init -{ - if (!(self = [super init])) - return nil; - - _enabled = YES; - - // As we have no targets yet, we start off with user interaction off. When a target is added, it'll get turned back on. - self.userInteractionEnabled = NO; - - return self; -} - -#if TARGET_OS_TV -- (void)didLoad -{ - [super didLoad]; - - // On tvOS all controls, such as buttons, interact with the focus system even if they don't have a target set on them. - // Here we add our own internal tap gesture to handle this behaviour. - self.userInteractionEnabled = YES; - UITapGestureRecognizer *tapGestureRec = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(_pressDown)]; - tapGestureRec.allowedPressTypes = @[@(UIPressTypeSelect)]; - [self.view addGestureRecognizer:tapGestureRec]; -} -#endif - -- (void)setUserInteractionEnabled:(BOOL)userInteractionEnabled -{ - [super setUserInteractionEnabled:userInteractionEnabled]; - self.isAccessibilityElement = userInteractionEnabled; -} - -- (void)__exitHierarchy -{ - [super __exitHierarchy]; - - // If a control node is exit the hierarchy and is tracking we have to cancel it - if (self.tracking) { - [self _cancelTrackingWithEvent:nil]; - } -} - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wobjc-missing-super-calls" - -#pragma mark - ASDisplayNode Overrides - -- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event -{ - // If we're not interested in touches, we have nothing to do. - if (!self.enabled) { - return; - } - - // Check if the tracking should start - UITouch *theTouch = [touches anyObject]; - if (![self beginTrackingWithTouch:theTouch withEvent:event]) { - return; - } - - // If we get more than one touch down on us, cancel. - // Additionally, if we're already tracking a touch, a second touch beginning is cause for cancellation. - if (touches.count > 1 || self.tracking) { - [self _cancelTrackingWithEvent:event]; - } else { - // Otherwise, begin tracking. - self.tracking = YES; - - // No need to check bounds on touchesBegan as we wouldn't get the call if it wasn't in our bounds. - self.touchInside = YES; - self.highlighted = YES; - - // Send the appropriate touch-down control event depending on how many times we've been tapped. - ASControlNodeEvent controlEventMask = (theTouch.tapCount == 1) ? ASControlNodeEventTouchDown : ASControlNodeEventTouchDownRepeat; - [self sendActionsForControlEvents:controlEventMask withEvent:event]; - } -} - -- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event -{ - // If we're not interested in touches, we have nothing to do. - if (!self.enabled) { - return; - } - - NSParameterAssert(touches.count == 1); - UITouch *theTouch = [touches anyObject]; - - // Check if tracking should continue - if (!self.tracking || ![self continueTrackingWithTouch:theTouch withEvent:event]) { - self.tracking = NO; - return; - } - - CGPoint touchLocation = [theTouch locationInView:self.view]; - - // Update our touchInside state. - BOOL dragIsInsideBounds = [self pointInside:touchLocation withEvent:nil]; - - // Update our highlighted state. - CGRect expandedBounds = _ASControlNodeGetExpandedBounds(self); - BOOL dragIsInsideExpandedBounds = CGRectContainsPoint(expandedBounds, touchLocation); - self.touchInside = dragIsInsideExpandedBounds; - self.highlighted = dragIsInsideExpandedBounds; - - [self sendActionsForControlEvents:(dragIsInsideBounds ? ASControlNodeEventTouchDragInside : ASControlNodeEventTouchDragOutside) - withEvent:event]; -} - -- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event -{ - // If we're not interested in touches, we have nothing to do. - if (!self.enabled) { - return; - } - - // Note that we've cancelled tracking. - [self _cancelTrackingWithEvent:event]; -} - -- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event -{ - // If we're not interested in touches, we have nothing to do. - if (!self.enabled) { - return; - } - - // On iPhone 6s, iOS 9.2 (and maybe other versions) sometimes calls -touchesEnded:withEvent: - // twice on the view for one call to -touchesBegan:withEvent:. On ASControlNode, it used to - // trigger an action twice unintentionally. Now, we ignore that event if we're not in a tracking - // state in order to have a correct behavior. - // It might be related to that issue: http://www.openradar.me/22910171 - if (!self.tracking) { - return; - } - - NSParameterAssert([touches count] == 1); - UITouch *theTouch = [touches anyObject]; - CGPoint touchLocation = [theTouch locationInView:self.view]; - - // Update state. - self.tracking = NO; - self.touchInside = NO; - self.highlighted = NO; - - // Note that we've ended tracking. - [self endTrackingWithTouch:theTouch withEvent:event]; - - // Send the appropriate touch-up control event. - CGRect expandedBounds = _ASControlNodeGetExpandedBounds(self); - BOOL touchUpIsInsideExpandedBounds = CGRectContainsPoint(expandedBounds, touchLocation); - - [self sendActionsForControlEvents:(touchUpIsInsideExpandedBounds ? ASControlNodeEventTouchUpInside : ASControlNodeEventTouchUpOutside) - withEvent:event]; -} - -- (void)_cancelTrackingWithEvent:(UIEvent *)event -{ - // We're no longer tracking and there is no touch to be inside. - self.tracking = NO; - self.touchInside = NO; - self.highlighted = NO; - - // Send the cancel event. - [self sendActionsForControlEvents:ASControlNodeEventTouchCancel withEvent:event]; -} - -#pragma clang diagnostic pop - -- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event -{ - ASDisplayNodeAssertMainThread(); - - // If not enabled we should not care about receving touches - if (! self.enabled) { - return nil; - } - - return [super hitTest:point withEvent:event]; -} - -- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer -{ - // If we're interested in touches, this is a tap (the only gesture we care about) and passed -hitTest for us, then no, you may not begin. Sir. - if (self.enabled && [gestureRecognizer isKindOfClass:[UITapGestureRecognizer class]] && gestureRecognizer.view != self.view) { - UITapGestureRecognizer *tapRecognizer = (UITapGestureRecognizer *)gestureRecognizer; - // Allow double-tap gestures - return tapRecognizer.numberOfTapsRequired != 1; - } - - // Otherwise, go ahead. :] - return YES; -} - -- (BOOL)supportsLayerBacking -{ - return super.supportsLayerBacking && !self.userInteractionEnabled; -} - -#pragma mark - Action Messages - -- (void)addTarget:(id)target action:(SEL)action forControlEvents:(ASControlNodeEvent)controlEventMask -{ - NSParameterAssert(action); - NSParameterAssert(controlEventMask != 0); - - // ASControlNode cannot be layer backed if adding a target - ASDisplayNodeAssert(!self.isLayerBacked, @"ASControlNode is layer backed, will never be able to call target in target:action: pair."); - - ASLockScopeSelf(); - - if (!_controlEventDispatchTable) { - _controlEventDispatchTable = [[NSMutableDictionary alloc] initWithCapacity:kASControlNodeEventDispatchTableInitialCapacity]; // enough to handle common types without re-hashing the dictionary when adding entries. - } - - // Create new target action pair - ASControlTargetAction *targetAction = [[ASControlTargetAction alloc] init]; - targetAction.action = action; - targetAction.target = target; - - // Enumerate the events in the mask, adding the target-action pair for each control event included in controlEventMask - _ASEnumerateControlEventsIncludedInMaskWithBlock(controlEventMask, ^ - (ASControlNodeEvent controlEvent) - { - // Do we already have an event table for this control event? - id eventKey = _ASControlNodeEventKeyForControlEvent(controlEvent); - NSMutableArray *eventTargetActionArray = _controlEventDispatchTable[eventKey]; - - if (!eventTargetActionArray) { - eventTargetActionArray = [[NSMutableArray alloc] init]; - } - - // Remove any prior target-action pair for this event, as UIKit does. - [eventTargetActionArray removeObject:targetAction]; - - // Register the new target-action as the last one to be sent. - [eventTargetActionArray addObject:targetAction]; - - if (eventKey) { - [_controlEventDispatchTable setObject:eventTargetActionArray forKey:eventKey]; - } - }); - - self.userInteractionEnabled = YES; -} - -- (NSArray *)actionsForTarget:(id)target forControlEvent:(ASControlNodeEvent)controlEvent -{ - NSParameterAssert(target); - NSParameterAssert(controlEvent != 0 && controlEvent != ASControlNodeEventAllEvents); - - ASLockScopeSelf(); - - // Grab the event target action array for this event. - NSMutableArray *eventTargetActionArray = _controlEventDispatchTable[_ASControlNodeEventKeyForControlEvent(controlEvent)]; - if (!eventTargetActionArray) { - return nil; - } - - NSMutableArray *actions = [[NSMutableArray alloc] init]; - - // Collect all actions for this target. - for (ASControlTargetAction *targetAction in eventTargetActionArray) { - if ((target == nil && targetAction.createdWithNoTarget) || (target != nil && target == targetAction.target)) { - [actions addObject:NSStringFromSelector(targetAction.action)]; - } - } - - return actions; -} - -- (NSSet *)allTargets -{ - ASLockScopeSelf(); - - NSMutableSet *targets = [[NSMutableSet alloc] init]; - - // Look at each event... - for (NSMutableArray *eventTargetActionArray in [_controlEventDispatchTable objectEnumerator]) { - // and each event's targets... - for (ASControlTargetAction *targetAction in eventTargetActionArray) { - [targets addObject:targetAction.target]; - } - } - - return targets; -} - -- (void)removeTarget:(id)target action:(SEL)action forControlEvents:(ASControlNodeEvent)controlEventMask -{ - NSParameterAssert(controlEventMask != 0); - - ASLockScopeSelf(); - - // Enumerate the events in the mask, removing the target-action pair for each control event included in controlEventMask. - _ASEnumerateControlEventsIncludedInMaskWithBlock(controlEventMask, ^ - (ASControlNodeEvent controlEvent) - { - // Grab the dispatch table for this event (if we have it). - id eventKey = _ASControlNodeEventKeyForControlEvent(controlEvent); - NSMutableArray *eventTargetActionArray = _controlEventDispatchTable[eventKey]; - if (!eventTargetActionArray) { - return; - } - - NSPredicate *filterPredicate = [NSPredicate predicateWithBlock:^BOOL(ASControlTargetAction *_Nullable evaluatedObject, NSDictionary * _Nullable bindings) { - if (!target || evaluatedObject.target == target) { - if (!action) { - return NO; - } else if (evaluatedObject.action == action) { - return NO; - } - } - - return YES; - }]; - [eventTargetActionArray filterUsingPredicate:filterPredicate]; - - if (eventTargetActionArray.count == 0) { - // If there are no targets for this event anymore, remove it. - [_controlEventDispatchTable removeObjectForKey:eventKey]; - } - }); -} - -#pragma mark - - -- (void)sendActionsForControlEvents:(ASControlNodeEvent)controlEvents withEvent:(UIEvent *)event -{ - ASDisplayNodeAssertMainThread(); //We access self.view below, it's not safe to call this off of main. - NSParameterAssert(controlEvents != 0); - - NSMutableArray *resolvedEventTargetActionArray = [[NSMutableArray alloc] init]; - - { - ASLockScopeSelf(); - - // Enumerate the events in the mask, invoking the target-action pairs for each. - _ASEnumerateControlEventsIncludedInMaskWithBlock(controlEvents, ^ - (ASControlNodeEvent controlEvent) - { - // Iterate on each target action pair - for (ASControlTargetAction *targetAction in _controlEventDispatchTable[_ASControlNodeEventKeyForControlEvent(controlEvent)]) { - ASControlTargetAction *resolvedTargetAction = [[ASControlTargetAction alloc] init]; - resolvedTargetAction.action = targetAction.action; - resolvedTargetAction.target = targetAction.target; - - // NSNull means that a nil target was set, so start at self and travel the responder chain - if (!resolvedTargetAction.target && targetAction.createdWithNoTarget) { - // if the target cannot perform the action, travel the responder chain to try to find something that does - resolvedTargetAction.target = [self.view targetForAction:resolvedTargetAction.action withSender:self]; - } - - if (resolvedTargetAction.target) { - [resolvedEventTargetActionArray addObject:resolvedTargetAction]; - } - } - }); - } - - //We don't want to hold the lock while calling out, we could potentially walk up the ownership tree causing a deadlock. -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Warc-performSelector-leaks" - for (ASControlTargetAction *targetAction in resolvedEventTargetActionArray) { - [targetAction.target performSelector:targetAction.action withObject:self withObject:event]; - } -#pragma clang diagnostic pop -} - -#pragma mark - Convenience - -id _ASControlNodeEventKeyForControlEvent(ASControlNodeEvent controlEvent) -{ - return @(controlEvent); -} - -void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, void (^block)(ASControlNodeEvent anEvent)) -{ - if (block == nil) { - return; - } - // Start with our first event (touch down) and work our way up to the last event (PrimaryActionTriggered) - for (ASControlNodeEvent thisEvent = ASControlNodeEventTouchDown; thisEvent <= ASControlNodeEventPrimaryActionTriggered; thisEvent <<= 1) { - // If it's included in the mask, invoke the block. - if ((mask & thisEvent) == thisEvent) - block(thisEvent); - } -} - -CGRect _ASControlNodeGetExpandedBounds(ASControlNode *controlNode) { - return CGRectInset(UIEdgeInsetsInsetRect(controlNode.view.bounds, controlNode.hitTestSlop), kASControlNodeExpandedInset, kASControlNodeExpandedInset); -} - -#pragma mark - For Subclasses - -- (BOOL)beginTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)touchEvent -{ - return YES; -} - -- (BOOL)continueTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)touchEvent -{ - return YES; -} - -- (void)cancelTrackingWithEvent:(UIEvent *)touchEvent -{ - // Subclass hook -} - -- (void)endTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)touchEvent -{ - // Subclass hook -} - -#pragma mark - Debug -- (ASDisplayNode *)debugHighlightOverlay -{ - return nil; -} -@end diff --git a/submodules/AsyncDisplayKit/Source/ASControlTargetAction.mm b/submodules/AsyncDisplayKit/Source/ASControlTargetAction.mm deleted file mode 100644 index 41cc113314..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASControlTargetAction.mm +++ /dev/null @@ -1,65 +0,0 @@ -// -// ASControlTargetAction.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -@implementation ASControlTargetAction -{ - __weak id _target; - BOOL _createdWithNoTarget; -} - -- (void)setTarget:(id)target { - _target = target; - - if (!target) { - _createdWithNoTarget = YES; - } -} - -- (id)target { - return _target; -} - -- (BOOL)isEqual:(id)object { - if (![object isKindOfClass:[ASControlTargetAction class]]) { - return NO; - } - - ASControlTargetAction *otherObject = (ASControlTargetAction *)object; - - BOOL areTargetsEqual; - - if (self.target != nil && otherObject.target != nil && self.target == otherObject.target) { - areTargetsEqual = YES; - } - else if (self.target == nil && otherObject.target == nil && self.createdWithNoTarget && otherObject.createdWithNoTarget) { - areTargetsEqual = YES; - } - else { - areTargetsEqual = NO; - } - - if (!areTargetsEqual) { - return NO; - } - - if (self.action && otherObject.action && self.action == otherObject.action) { - return YES; - } - else { - return NO; - } -} - -- (NSUInteger)hash { - return [self.target hash]; -} - -@end diff --git a/submodules/AsyncDisplayKit/Source/ASDimension.mm b/submodules/AsyncDisplayKit/Source/ASDimension.mm deleted file mode 100644 index d1a42462df..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASDimension.mm +++ /dev/null @@ -1,125 +0,0 @@ -// -// ASDimension.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#import - -#import - -#pragma mark - ASDimension - -ASDimension const ASDimensionAuto = {ASDimensionUnitAuto, 0}; - -ASOVERLOADABLE ASDimension ASDimensionMake(NSString *dimension) -{ - if (dimension.length > 0) { - - // Handle points - if ([dimension hasSuffix:@"pt"]) { - return ASDimensionMake(ASDimensionUnitPoints, ASCGFloatFromString(dimension)); - } - - // Handle auto - if ([dimension isEqualToString:@"auto"]) { - return ASDimensionAuto; - } - - // Handle percent - if ([dimension hasSuffix:@"%"]) { - return ASDimensionMake(ASDimensionUnitFraction, (ASCGFloatFromString(dimension) / 100.0)); - } - } - - return ASDimensionAuto; -} - -NSString *NSStringFromASDimension(ASDimension dimension) -{ - switch (dimension.unit) { - case ASDimensionUnitPoints: - return [NSString stringWithFormat:@"%.0fpt", dimension.value]; - case ASDimensionUnitFraction: - return [NSString stringWithFormat:@"%.0f%%", dimension.value * 100.0]; - case ASDimensionUnitAuto: - return @"Auto"; - } -} - -#pragma mark - ASLayoutSize - -ASLayoutSize const ASLayoutSizeAuto = {ASDimensionAuto, ASDimensionAuto}; - -#pragma mark - ASSizeRange - -ASSizeRange const ASSizeRangeZero = {}; - -ASSizeRange const ASSizeRangeUnconstrained = { {0, 0}, { INFINITY, INFINITY }}; - -struct _Range { - CGFloat min; - CGFloat max; - - /** - Intersects another dimension range. If the other range does not overlap, this size range "wins" by returning a - single point within its own range that is closest to the non-overlapping range. - */ - _Range intersect(const _Range &other) const - { - CGFloat newMin = MAX(min, other.min); - CGFloat newMax = MIN(max, other.max); - if (newMin <= newMax) { - return {newMin, newMax}; - } else { - // No intersection. If we're before the other range, return our max; otherwise our min. - if (min < other.min) { - return {max, max}; - } else { - return {min, min}; - } - } - } -}; - -ASSizeRange ASSizeRangeIntersect(ASSizeRange sizeRange, ASSizeRange otherSizeRange) -{ - const auto w = _Range({sizeRange.min.width, sizeRange.max.width}).intersect({otherSizeRange.min.width, otherSizeRange.max.width}); - const auto h = _Range({sizeRange.min.height, sizeRange.max.height}).intersect({otherSizeRange.min.height, otherSizeRange.max.height}); - return {{w.min, h.min}, {w.max, h.max}}; -} - -NSString *NSStringFromASSizeRange(ASSizeRange sizeRange) -{ - // 17 field length copied from iOS 10.3 impl of NSStringFromCGSize. - if (CGSizeEqualToSize(sizeRange.min, sizeRange.max)) { - return [NSString stringWithFormat:@"{{%.*g, %.*g}}", - 17, sizeRange.min.width, - 17, sizeRange.min.height]; - } - return [NSString stringWithFormat:@"{{%.*g, %.*g}, {%.*g, %.*g}}", - 17, sizeRange.min.width, - 17, sizeRange.min.height, - 17, sizeRange.max.width, - 17, sizeRange.max.height]; -} - -#if YOGA -#pragma mark - Yoga - ASEdgeInsets -ASEdgeInsets const ASEdgeInsetsZero = {}; - -ASEdgeInsets ASEdgeInsetsMake(UIEdgeInsets edgeInsets) -{ - ASEdgeInsets asEdgeInsets = ASEdgeInsetsZero; - asEdgeInsets.top = ASDimensionMake(edgeInsets.top); - asEdgeInsets.left = ASDimensionMake(edgeInsets.left); - asEdgeInsets.bottom = ASDimensionMake(edgeInsets.bottom); - asEdgeInsets.right = ASDimensionMake(edgeInsets.right); - return asEdgeInsets; -} -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASDimensionInternal.mm b/submodules/AsyncDisplayKit/Source/ASDimensionInternal.mm deleted file mode 100644 index 8af3555252..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASDimensionInternal.mm +++ /dev/null @@ -1,65 +0,0 @@ -// -// ASDimensionInternal.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#pragma mark - ASLayoutElementSize - -NSString *NSStringFromASLayoutElementSize(ASLayoutElementSize size) -{ - return [NSString stringWithFormat: - @"", - NSStringFromASLayoutSize(ASLayoutSizeMake(size.width, size.height)), - NSStringFromASLayoutSize(ASLayoutSizeMake(size.minWidth, size.minHeight)), - NSStringFromASLayoutSize(ASLayoutSizeMake(size.maxWidth, size.maxHeight))]; -} - -ASDISPLAYNODE_INLINE void ASLayoutElementSizeConstrain(CGFloat minVal, CGFloat exactVal, CGFloat maxVal, CGFloat *outMin, CGFloat *outMax) -{ - NSCAssert(!isnan(minVal), @"minVal must not be NaN"); - NSCAssert(!isnan(maxVal), @"maxVal must not be NaN"); - // Avoid use of min/max primitives since they're harder to reason - // about in the presence of NaN (in exactVal) - // Follow CSS: min overrides max overrides exact. - - // Begin with the min/max range - *outMin = minVal; - *outMax = maxVal; - if (maxVal <= minVal) { - // min overrides max and exactVal is irrelevant - *outMax = minVal; - return; - } - if (isnan(exactVal)) { - // no exact value, so leave as a min/max range - return; - } - if (exactVal > maxVal) { - // clip to max value - *outMin = maxVal; - } else if (exactVal < minVal) { - // clip to min value - *outMax = minVal; - } else { - // use exact value - *outMin = *outMax = exactVal; - } -} - -ASSizeRange ASLayoutElementSizeResolveAutoSize(ASLayoutElementSize size, const CGSize parentSize, ASSizeRange autoASSizeRange) -{ - CGSize resolvedExact = ASLayoutSizeResolveSize(ASLayoutSizeMake(size.width, size.height), parentSize, {NAN, NAN}); - CGSize resolvedMin = ASLayoutSizeResolveSize(ASLayoutSizeMake(size.minWidth, size.minHeight), parentSize, autoASSizeRange.min); - CGSize resolvedMax = ASLayoutSizeResolveSize(ASLayoutSizeMake(size.maxWidth, size.maxHeight), parentSize, autoASSizeRange.max); - - CGSize rangeMin, rangeMax; - ASLayoutElementSizeConstrain(resolvedMin.width, resolvedExact.width, resolvedMax.width, &rangeMin.width, &rangeMax.width); - ASLayoutElementSizeConstrain(resolvedMin.height, resolvedExact.height, resolvedMax.height, &rangeMin.height, &rangeMax.height); - return {rangeMin, rangeMax}; -} diff --git a/submodules/AsyncDisplayKit/Source/ASDispatch.h b/submodules/AsyncDisplayKit/Source/ASDispatch.h deleted file mode 100644 index e20941806f..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASDispatch.h +++ /dev/null @@ -1,27 +0,0 @@ -// -// ASDispatch.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import - -/** - * Like dispatch_apply, but you can set the thread count. 0 means 2*active CPUs. - * - * Note: The actual number of threads may be lower than threadCount, if libdispatch - * decides the system can't handle it. In reality this rarely happens. - */ -AS_EXTERN void ASDispatchApply(size_t iterationCount, dispatch_queue_t queue, NSUInteger threadCount, NS_NOESCAPE void(^work)(size_t i)); - -/** - * Like dispatch_async, but you can set the thread count. 0 means 2*active CPUs. - * - * Note: The actual number of threads may be lower than threadCount, if libdispatch - * decides the system can't handle it. In reality this rarely happens. - */ -AS_EXTERN void ASDispatchAsync(size_t iterationCount, dispatch_queue_t queue, NSUInteger threadCount, NS_NOESCAPE void(^work)(size_t i)); diff --git a/submodules/AsyncDisplayKit/Source/ASDispatch.mm b/submodules/AsyncDisplayKit/Source/ASDispatch.mm deleted file mode 100644 index edc2feba46..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASDispatch.mm +++ /dev/null @@ -1,63 +0,0 @@ -// -// ASDispatch.mm -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import "ASDispatch.h" -#import - - -// Prefer C atomics in this file because ObjC blocks can't capture C++ atomics well. -#import - -/** - * Like dispatch_apply, but you can set the thread count. 0 means 2*active CPUs. - * - * Note: The actual number of threads may be lower than threadCount, if libdispatch - * decides the system can't handle it. In reality this rarely happens. - */ -void ASDispatchApply(size_t iterationCount, dispatch_queue_t queue, NSUInteger threadCount, NS_NOESCAPE void(^work)(size_t i)) { - if (threadCount == 0) { - if (ASActivateExperimentalFeature(ASExperimentalDispatchApply)) { - dispatch_apply(iterationCount, queue, work); - return; - } - threadCount = NSProcessInfo.processInfo.activeProcessorCount * 2; - } - dispatch_group_t group = dispatch_group_create(); - __block atomic_size_t counter = ATOMIC_VAR_INIT(0); - for (NSUInteger t = 0; t < threadCount; t++) { - dispatch_group_async(group, queue, ^{ - size_t i; - while ((i = atomic_fetch_add(&counter, 1)) < iterationCount) { - work(i); - } - }); - } - dispatch_group_wait(group, DISPATCH_TIME_FOREVER); -}; - -/** - * Like dispatch_async, but you can set the thread count. 0 means 2*active CPUs. - * - * Note: The actual number of threads may be lower than threadCount, if libdispatch - * decides the system can't handle it. In reality this rarely happens. - */ -void ASDispatchAsync(size_t iterationCount, dispatch_queue_t queue, NSUInteger threadCount, NS_NOESCAPE void(^work)(size_t i)) { - if (threadCount == 0) { - threadCount = NSProcessInfo.processInfo.activeProcessorCount * 2; - } - __block atomic_size_t counter = ATOMIC_VAR_INIT(0); - for (NSUInteger t = 0; t < threadCount; t++) { - dispatch_async(queue, ^{ - size_t i; - while ((i = atomic_fetch_add(&counter, 1)) < iterationCount) { - work(i); - } - }); - } -}; - diff --git a/submodules/AsyncDisplayKit/Source/ASDisplayNode+Ancestry.mm b/submodules/AsyncDisplayKit/Source/ASDisplayNode+Ancestry.mm deleted file mode 100644 index ea1376ed54..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASDisplayNode+Ancestry.mm +++ /dev/null @@ -1,90 +0,0 @@ -// -// ASDisplayNode+Ancestry.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import -#import - -AS_SUBCLASSING_RESTRICTED -@interface ASNodeAncestryEnumerator : NSEnumerator -@end - -@implementation ASNodeAncestryEnumerator { - ASDisplayNode *_lastNode; // This needs to be strong because enumeration will not retain the current batch of objects - BOOL _initialState; -} - -- (instancetype)initWithNode:(ASDisplayNode *)node -{ - if (self = [super init]) { - _initialState = YES; - _lastNode = node; - } - return self; -} - -- (id)nextObject -{ - if (_initialState) { - _initialState = NO; - return _lastNode; - } - - ASDisplayNode *nextNode = _lastNode.supernode; - if (nextNode == nil && ASDisplayNodeThreadIsMain()) { - CALayer *layer = _lastNode.nodeLoaded ? _lastNode.layer.superlayer : nil; - while (layer != nil) { - nextNode = ASLayerToDisplayNode(layer); - if (nextNode != nil) { - break; - } - layer = layer.superlayer; - } - } - _lastNode = nextNode; - return nextNode; -} - -@end - -@implementation ASDisplayNode (Ancestry) - -- (id)supernodes -{ - NSEnumerator *result = [[ASNodeAncestryEnumerator alloc] initWithNode:self]; - [result nextObject]; // discard first object (self) - return result; -} - -- (id)supernodesIncludingSelf -{ - return [[ASNodeAncestryEnumerator alloc] initWithNode:self]; -} - -- (nullable __kindof ASDisplayNode *)supernodeOfClass:(Class)supernodeClass includingSelf:(BOOL)includeSelf -{ - id chain = includeSelf ? self.supernodesIncludingSelf : self.supernodes; - for (ASDisplayNode *ancestor in chain) { - if ([ancestor isKindOfClass:supernodeClass]) { - return ancestor; - } - } - return nil; -} - -- (NSString *)ancestryDescription -{ - NSMutableArray *strings = [NSMutableArray array]; - for (ASDisplayNode *node in self.supernodes) { - [strings addObject:ASObjectDescriptionMakeTiny(node)]; - } - return strings.description; -} - -@end diff --git a/submodules/AsyncDisplayKit/Source/ASDisplayNode+AsyncDisplay.mm b/submodules/AsyncDisplayKit/Source/ASDisplayNode+AsyncDisplay.mm deleted file mode 100644 index 068f5509a7..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASDisplayNode+AsyncDisplay.mm +++ /dev/null @@ -1,493 +0,0 @@ -// -// ASDisplayNode+AsyncDisplay.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import -#import -#import -#import "ASDisplayNodeInternal.h" -#import -#import -#import -#import "ASSignpost.h" -#import - -using AS::MutexLocker; - -@interface ASDisplayNode () <_ASDisplayLayerDelegate> -@end - -@implementation ASDisplayNode (AsyncDisplay) - -#if ASDISPLAYNODE_DELAY_DISPLAY - #define ASDN_DELAY_FOR_DISPLAY() usleep( (long)(0.1 * USEC_PER_SEC) ) -#else - #define ASDN_DELAY_FOR_DISPLAY() -#endif - -#define CHECK_CANCELLED_AND_RETURN_NIL(expr) if (isCancelledBlock()) { \ - expr; \ - return nil; \ - } \ - -- (NSObject *)drawParameters -{ - __instanceLock__.lock(); - BOOL implementsDrawParameters = _flags.implementsDrawParameters; - __instanceLock__.unlock(); - - if (implementsDrawParameters) { - return [self drawParametersForAsyncLayer:self.asyncLayer]; - } else { - return nil; - } -} - -- (void)_recursivelyRasterizeSelfAndSublayersWithIsCancelledBlock:(asdisplaynode_iscancelled_block_t)isCancelledBlock displayBlocks:(NSMutableArray *)displayBlocks -{ - // Skip subtrees that are hidden or zero alpha. - if (self.isHidden || self.alpha <= 0.0) { - return; - } - - __instanceLock__.lock(); - BOOL rasterizingFromAscendent = (_hierarchyState & ASHierarchyStateRasterized); - __instanceLock__.unlock(); - - // if super node is rasterizing descendants, subnodes will not have had layout calls because they don't have layers - if (rasterizingFromAscendent) { - [self __layout]; - } - - // Capture these outside the display block so they are retained. - UIColor *backgroundColor = self.backgroundColor; - CGRect bounds = self.bounds; - CGFloat cornerRadius = self.cornerRadius; - BOOL clipsToBounds = self.clipsToBounds; - - CGRect frame; - - // If this is the root container node, use a frame with a zero origin to draw into. If not, calculate the correct frame using the node's position, transform and anchorPoint. - if (self.rasterizesSubtree) { - frame = CGRectMake(0.0f, 0.0f, bounds.size.width, bounds.size.height); - } else { - CGPoint position = self.position; - CGPoint anchorPoint = self.anchorPoint; - - // Pretty hacky since full 3D transforms aren't actually supported, but attempt to compute the transformed frame of this node so that we can composite it into approximately the right spot. - CGAffineTransform transform = CATransform3DGetAffineTransform(self.transform); - CGSize scaledBoundsSize = CGSizeApplyAffineTransform(bounds.size, transform); - CGPoint origin = CGPointMake(position.x - scaledBoundsSize.width * anchorPoint.x, - position.y - scaledBoundsSize.height * anchorPoint.y); - frame = CGRectMake(origin.x, origin.y, bounds.size.width, bounds.size.height); - } - - // Get the display block for this node. - asyncdisplaykit_async_transaction_operation_block_t displayBlock = [self _displayBlockWithAsynchronous:NO isCancelledBlock:isCancelledBlock rasterizing:YES]; - - // We'll display something if there is a display block, clipping, translation and/or a background color. - BOOL shouldDisplay = displayBlock || backgroundColor || CGPointEqualToPoint(CGPointZero, frame.origin) == NO || clipsToBounds; - - // If we should display, then push a transform, draw the background color, and draw the contents. - // The transform is popped in a block added after the recursion into subnodes. - if (shouldDisplay) { - dispatch_block_t pushAndDisplayBlock = ^{ - // Push transform relative to parent. - CGContextRef context = UIGraphicsGetCurrentContext(); - CGContextSaveGState(context); - - CGContextTranslateCTM(context, frame.origin.x, frame.origin.y); - - //support cornerRadius - if (rasterizingFromAscendent && clipsToBounds) { - if (cornerRadius) { - [[UIBezierPath bezierPathWithRoundedRect:bounds cornerRadius:cornerRadius] addClip]; - } else { - CGContextClipToRect(context, bounds); - } - } - - // Fill background if any. - CGColorRef backgroundCGColor = backgroundColor.CGColor; - if (backgroundColor && CGColorGetAlpha(backgroundCGColor) > 0.0) { - CGContextSetFillColorWithColor(context, backgroundCGColor); - CGContextFillRect(context, bounds); - } - - // If there is a display block, call it to get the image, then copy the image into the current context (which is the rasterized container's backing store). - if (displayBlock) { - UIImage *image = (UIImage *)displayBlock(); - if (image) { - BOOL opaque = ASImageAlphaInfoIsOpaque(CGImageGetAlphaInfo(image.CGImage)); - CGBlendMode blendMode = opaque ? kCGBlendModeCopy : kCGBlendModeNormal; - [image drawInRect:bounds blendMode:blendMode alpha:1]; - } - } - }; - [displayBlocks addObject:pushAndDisplayBlock]; - } - - // Recursively capture displayBlocks for all descendants. - for (ASDisplayNode *subnode in self.subnodes) { - [subnode _recursivelyRasterizeSelfAndSublayersWithIsCancelledBlock:isCancelledBlock displayBlocks:displayBlocks]; - } - - // If we pushed a transform, pop it by adding a display block that does nothing other than that. - if (shouldDisplay) { - // Since this block is pure, we can store it statically. - static dispatch_block_t popBlock; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - popBlock = ^{ - CGContextRef context = UIGraphicsGetCurrentContext(); - CGContextRestoreGState(context); - }; - }); - [displayBlocks addObject:popBlock]; - } -} - -- (asyncdisplaykit_async_transaction_operation_block_t)_displayBlockWithAsynchronous:(BOOL)asynchronous - isCancelledBlock:(asdisplaynode_iscancelled_block_t)isCancelledBlock - rasterizing:(BOOL)rasterizing -{ - ASDisplayNodeAssertMainThread(); - - asyncdisplaykit_async_transaction_operation_block_t displayBlock = nil; - ASDisplayNodeFlags flags; - - __instanceLock__.lock(); - - flags = _flags; - - // We always create a graphics context, unless a -display method is used, OR if we are a subnode drawing into a rasterized parent. - BOOL shouldCreateGraphicsContext = (flags.implementsImageDisplay == NO && rasterizing == NO); - BOOL shouldBeginRasterizing = (rasterizing == NO && flags.rasterizesSubtree); - BOOL usesImageDisplay = flags.implementsImageDisplay; - BOOL usesDrawRect = flags.implementsDrawRect; - - if (usesImageDisplay == NO && usesDrawRect == NO && shouldBeginRasterizing == NO) { - // Early exit before requesting more expensive properties like bounds and opaque from the layer. - __instanceLock__.unlock(); - return nil; - } - - BOOL opaque = self.opaque; - CGRect bounds = self.bounds; - UIColor *backgroundColor = self.backgroundColor; - CGColorRef borderColor = self.borderColor; - CGFloat borderWidth = self.borderWidth; - CGFloat contentsScaleForDisplay = _contentsScaleForDisplay; - - __instanceLock__.unlock(); - - // Capture drawParameters from delegate on main thread, if this node is displaying itself rather than recursively rasterizing. - id drawParameters = (shouldBeginRasterizing == NO ? [self drawParameters] : nil); - - // Only the -display methods should be called if we can't size the graphics buffer to use. - if (CGRectIsEmpty(bounds) && (shouldBeginRasterizing || shouldCreateGraphicsContext)) { - return nil; - } - - ASDisplayNodeAssert(contentsScaleForDisplay != 0.0, @"Invalid contents scale"); - ASDisplayNodeAssert(rasterizing || !(_hierarchyState & ASHierarchyStateRasterized), - @"Rasterized descendants should never display unless being drawn into the rasterized container."); - - if (shouldBeginRasterizing) { - // Collect displayBlocks for all descendants. - NSMutableArray *displayBlocks = [[NSMutableArray alloc] init]; - [self _recursivelyRasterizeSelfAndSublayersWithIsCancelledBlock:isCancelledBlock displayBlocks:displayBlocks]; - CHECK_CANCELLED_AND_RETURN_NIL(); - - // If [UIColor clearColor] or another semitransparent background color is used, include alpha channel when rasterizing. - // Unlike CALayer drawing, we include the backgroundColor as a base during rasterization. - opaque = opaque && CGColorGetAlpha(backgroundColor.CGColor) == 1.0f; - - displayBlock = ^id{ - CHECK_CANCELLED_AND_RETURN_NIL(); - - ASGraphicsBeginImageContextWithOptions(bounds.size, opaque, contentsScaleForDisplay); - - for (dispatch_block_t block in displayBlocks) { - CHECK_CANCELLED_AND_RETURN_NIL(ASGraphicsEndImageContext()); - block(); - } - - UIImage *image = ASGraphicsGetImageAndEndCurrentContext(); - - ASDN_DELAY_FOR_DISPLAY(); - return image; - }; - } else { - displayBlock = ^id{ - CHECK_CANCELLED_AND_RETURN_NIL(); - - if (shouldCreateGraphicsContext) { - ASGraphicsBeginImageContextWithOptions(bounds.size, opaque, contentsScaleForDisplay); - CHECK_CANCELLED_AND_RETURN_NIL( ASGraphicsEndImageContext(); ); - } - - CGContextRef currentContext = UIGraphicsGetCurrentContext(); - UIImage *image = nil; - - if (shouldCreateGraphicsContext && !currentContext) { - //ASDisplayNodeAssert(NO, @"Failed to create a CGContext (size: %@)", NSStringFromCGSize(bounds.size)); - return nil; - } - - // For -display methods, we don't have a context, and thus will not call the _willDisplayNodeContentWithRenderingContext or - // _didDisplayNodeContentWithRenderingContext blocks. It's up to the implementation of -display... to do what it needs. - [self __willDisplayNodeContentWithRenderingContext:currentContext drawParameters:drawParameters]; - - if (usesImageDisplay) { // If we are using a display method, we'll get an image back directly. - image = [self.class displayWithParameters:drawParameters isCancelled:isCancelledBlock]; - } else if (usesDrawRect) { // If we're using a draw method, this will operate on the currentContext. - [self.class drawRect:bounds withParameters:drawParameters isCancelled:isCancelledBlock isRasterizing:rasterizing]; - } - - [self __didDisplayNodeContentWithRenderingContext:currentContext image:&image drawParameters:drawParameters backgroundColor:backgroundColor borderWidth:borderWidth borderColor:borderColor]; - - if (shouldCreateGraphicsContext) { - CHECK_CANCELLED_AND_RETURN_NIL( ASGraphicsEndImageContext(); ); - image = ASGraphicsGetImageAndEndCurrentContext(); - } - - ASDN_DELAY_FOR_DISPLAY(); - return image; - }; - } - - /** - If we're profiling, wrap the display block with signpost start and end. - Color the interval red if cancelled, green otherwise. - */ -#if AS_KDEBUG_ENABLE - __unsafe_unretained id ptrSelf = self; - displayBlock = ^{ - ASSignpostStartCustom(ASSignpostLayerDisplay, ptrSelf, 0); - id result = displayBlock(); - ASSignpostEndCustom(ASSignpostLayerDisplay, ptrSelf, 0, isCancelledBlock() ? ASSignpostColorRed : ASSignpostColorGreen); - return result; - }; -#endif - - return displayBlock; -} - -- (void)__willDisplayNodeContentWithRenderingContext:(CGContextRef)context drawParameters:(id _Nullable)drawParameters -{ - if (context) { - __instanceLock__.lock(); - ASCornerRoundingType cornerRoundingType = _cornerRoundingType; - CGFloat cornerRadius = _cornerRadius; - ASDisplayNodeContextModifier willDisplayNodeContentWithRenderingContext = _willDisplayNodeContentWithRenderingContext; - __instanceLock__.unlock(); - - if (cornerRoundingType == ASCornerRoundingTypePrecomposited && cornerRadius > 0.0) { - ASDisplayNodeAssert(context == UIGraphicsGetCurrentContext(), @"context is expected to be pushed on UIGraphics stack %@", self); - // TODO: This clip path should be removed if we are rasterizing. - CGRect boundingBox = CGContextGetClipBoundingBox(context); - [[UIBezierPath bezierPathWithRoundedRect:boundingBox cornerRadius:cornerRadius] addClip]; - } - - if (willDisplayNodeContentWithRenderingContext) { - willDisplayNodeContentWithRenderingContext(context, drawParameters); - } - } - -} -- (void)__didDisplayNodeContentWithRenderingContext:(CGContextRef)context image:(UIImage **)image drawParameters:(id _Nullable)drawParameters backgroundColor:(UIColor *)backgroundColor borderWidth:(CGFloat)borderWidth borderColor:(CGColorRef)borderColor -{ - if (context == NULL && *image == NULL) { - return; - } - - __instanceLock__.lock(); - ASCornerRoundingType cornerRoundingType = _cornerRoundingType; - CGFloat cornerRadius = _cornerRadius; - CGFloat contentsScale = _contentsScaleForDisplay; - ASDisplayNodeContextModifier didDisplayNodeContentWithRenderingContext = _didDisplayNodeContentWithRenderingContext; - __instanceLock__.unlock(); - - if (context != NULL) { - if (didDisplayNodeContentWithRenderingContext) { - didDisplayNodeContentWithRenderingContext(context, drawParameters); - } - } - - if (cornerRoundingType == ASCornerRoundingTypePrecomposited && cornerRadius > 0.0f) { - CGRect bounds = CGRectZero; - if (context == NULL) { - bounds = self.threadSafeBounds; - bounds.size.width *= contentsScale; - bounds.size.height *= contentsScale; - CGFloat white = 0.0f, alpha = 0.0f; - [backgroundColor getWhite:&white alpha:&alpha]; - ASGraphicsBeginImageContextWithOptions(bounds.size, (alpha == 1.0f), contentsScale); - [*image drawInRect:bounds]; - } else { - bounds = CGContextGetClipBoundingBox(context); - } - - ASDisplayNodeAssert(UIGraphicsGetCurrentContext(), @"context is expected to be pushed on UIGraphics stack %@", self); - - UIBezierPath *roundedHole = [UIBezierPath bezierPathWithRect:bounds]; - [roundedHole appendPath:[UIBezierPath bezierPathWithRoundedRect:bounds cornerRadius:cornerRadius * contentsScale]]; - roundedHole.usesEvenOddFillRule = YES; - - UIBezierPath *roundedPath = nil; - if (borderWidth > 0.0f) { // Don't create roundedPath and stroke if borderWidth is 0.0 - CGFloat strokeThickness = borderWidth * contentsScale; - CGFloat strokeInset = ((strokeThickness + 1.0f) / 2.0f) - 1.0f; - roundedPath = [UIBezierPath bezierPathWithRoundedRect:CGRectInset(bounds, strokeInset, strokeInset) - cornerRadius:_cornerRadius * contentsScale]; - roundedPath.lineWidth = strokeThickness; - [[UIColor colorWithCGColor:borderColor] setStroke]; - } - - // Punch out the corners by copying the backgroundColor over them. - // This works for everything from clearColor to opaque colors. - [backgroundColor setFill]; - [roundedHole fillWithBlendMode:kCGBlendModeCopy alpha:1.0f]; - - [roundedPath stroke]; // Won't do anything if borderWidth is 0 and roundedPath is nil. - - if (*image) { - *image = ASGraphicsGetImageAndEndCurrentContext(); - } - } -} - -- (void)displayAsyncLayer:(_ASDisplayLayer *)asyncLayer asynchronously:(BOOL)asynchronously -{ - ASDisplayNodeAssertMainThread(); - - __instanceLock__.lock(); - - if (_hierarchyState & ASHierarchyStateRasterized) { - __instanceLock__.unlock(); - return; - } - - CALayer *layer = _layer; - BOOL rasterizesSubtree = _flags.rasterizesSubtree; - - __instanceLock__.unlock(); - - // for async display, capture the current displaySentinel value to bail early when the job is executed if another is - // enqueued - // for sync display, do not support cancellation - - // FIXME: what about the degenerate case where we are calling setNeedsDisplay faster than the jobs are dequeuing - // from the displayQueue? Need to not cancel early fails from displaySentinel changes. - asdisplaynode_iscancelled_block_t isCancelledBlock = nil; - if (asynchronously) { - uint displaySentinelValue = ++_displaySentinel; - __weak ASDisplayNode *weakSelf = self; - isCancelledBlock = ^BOOL{ - __strong ASDisplayNode *self = weakSelf; - return self == nil || (displaySentinelValue != self->_displaySentinel.load()); - }; - } else { - isCancelledBlock = ^BOOL{ - return NO; - }; - } - - // Set up displayBlock to call either display or draw on the delegate and return a UIImage contents - asyncdisplaykit_async_transaction_operation_block_t displayBlock = [self _displayBlockWithAsynchronous:asynchronously isCancelledBlock:isCancelledBlock rasterizing:NO]; - - if (!displayBlock) { - return; - } - - ASDisplayNodeAssert(layer, @"Expect _layer to be not nil"); - - // This block is called back on the main thread after rendering at the completion of the current async transaction, or immediately if !asynchronously - asyncdisplaykit_async_transaction_operation_completion_block_t completionBlock = ^(id value, BOOL canceled){ - ASDisplayNodeCAssertMainThread(); - if (!canceled && !isCancelledBlock()) { - UIImage *image = (UIImage *)value; - BOOL stretchable = (NO == UIEdgeInsetsEqualToEdgeInsets(image.capInsets, UIEdgeInsetsZero)); - if (stretchable) { - ASDisplayNodeSetResizableContents(layer, image); - } else { - layer.contentsScale = self.contentsScale; - layer.contents = (id)image.CGImage; - } - [self didDisplayAsyncLayer:self.asyncLayer]; - - if (rasterizesSubtree) { - ASDisplayNodePerformBlockOnEverySubnode(self, NO, ^(ASDisplayNode * _Nonnull node) { - [node didDisplayAsyncLayer:node.asyncLayer]; - }); - } - } - }; - - // Call willDisplay immediately in either case - [self willDisplayAsyncLayer:self.asyncLayer asynchronously:asynchronously]; - - if (rasterizesSubtree) { - ASDisplayNodePerformBlockOnEverySubnode(self, NO, ^(ASDisplayNode * _Nonnull node) { - [node willDisplayAsyncLayer:node.asyncLayer asynchronously:asynchronously]; - }); - } - - if (asynchronously) { - // Async rendering operations are contained by a transaction, which allows them to proceed and concurrently - // while synchronizing the final application of the results to the layer's contents property (completionBlock). - - // First, look to see if we are expected to join a parent's transaction container. - CALayer *containerLayer = layer.asyncdisplaykit_parentTransactionContainer ? : layer; - - // In the case that a transaction does not yet exist (such as for an individual node outside of a container), - // this call will allocate the transaction and add it to _ASAsyncTransactionGroup. - // It will automatically commit the transaction at the end of the runloop. - _ASAsyncTransaction *transaction = containerLayer.asyncdisplaykit_asyncTransaction; - - // Adding this displayBlock operation to the transaction will start it IMMEDIATELY. - // The only function of the transaction commit is to gate the calling of the completionBlock. - [transaction addOperationWithBlock:displayBlock priority:self.drawingPriority queue:[_ASDisplayLayer displayQueue] completion:completionBlock]; - } else { - UIImage *contents = (UIImage *)displayBlock(); - completionBlock(contents, NO); - } -} - -- (void)cancelDisplayAsyncLayer:(_ASDisplayLayer *)asyncLayer -{ - _displaySentinel.fetch_add(1); -} - -- (ASDisplayNodeContextModifier)willDisplayNodeContentWithRenderingContext -{ - MutexLocker l(__instanceLock__); - return _willDisplayNodeContentWithRenderingContext; -} - -- (ASDisplayNodeContextModifier)didDisplayNodeContentWithRenderingContext -{ - MutexLocker l(__instanceLock__); - return _didDisplayNodeContentWithRenderingContext; -} - -- (void)setWillDisplayNodeContentWithRenderingContext:(ASDisplayNodeContextModifier)contextModifier -{ - MutexLocker l(__instanceLock__); - _willDisplayNodeContentWithRenderingContext = contextModifier; -} - -- (void)setDidDisplayNodeContentWithRenderingContext:(ASDisplayNodeContextModifier)contextModifier; -{ - MutexLocker l(__instanceLock__); - _didDisplayNodeContentWithRenderingContext = contextModifier; -} - -@end diff --git a/submodules/AsyncDisplayKit/Source/ASDisplayNode+Convenience.mm b/submodules/AsyncDisplayKit/Source/ASDisplayNode+Convenience.mm deleted file mode 100644 index 29d761be5a..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASDisplayNode+Convenience.mm +++ /dev/null @@ -1,40 +0,0 @@ -// -// ASDisplayNode+Convenience.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#import - -#import -#import "ASResponderChainEnumerator.h" - -@implementation ASDisplayNode (Convenience) - -- (__kindof UIViewController *)closestViewController -{ - ASDisplayNodeAssertMainThread(); - - // Careful not to trigger node loading here. - if (!self.nodeLoaded) { - return nil; - } - - // Get the closest view. - UIView *view = ASFindClosestViewOfLayer(self.layer); - // Travel up the responder chain to find a view controller. - for (UIResponder *responder in [view asdk_responderChainEnumerator]) { - UIViewController *vc = ASDynamicCast(responder, UIViewController); - if (vc != nil) { - return vc; - } - } - return nil; -} - -@end diff --git a/submodules/AsyncDisplayKit/Source/ASDisplayNode+Deprecated.h b/submodules/AsyncDisplayKit/Source/ASDisplayNode+Deprecated.h deleted file mode 100644 index b05390ea47..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASDisplayNode+Deprecated.h +++ /dev/null @@ -1,142 +0,0 @@ -// -// ASDisplayNode+Deprecated.h -// Texture -// -// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. -// This source code is licensed under the BSD-style license found in the -// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional -// grant of patent rights can be found in the PATENTS file in the same directory. -// -// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present, -// Pinterest, Inc. Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// - -#pragma once - -#import - -@interface ASDisplayNode (Deprecated) - -/** - * @abstract The name of this node, which will be displayed in `description`. The default value is nil. - * - * @deprecated Deprecated in version 2.0: Use .debugName instead. This value will display in - * results of the -asciiArtString method (@see ASLayoutElementAsciiArtProtocol). - */ -@property (nullable, nonatomic, copy) NSString *name ASDISPLAYNODE_DEPRECATED_MSG("Use .debugName instead."); - -/** - * @abstract Provides a default intrinsic content size for calculateSizeThatFits:. This is useful when laying out - * a node that either has no intrinsic content size or should be laid out at a different size than its intrinsic content - * size. For example, this property could be set on an ASImageNode to display at a size different from the underlying - * image size. - * - * @return Try to create a CGSize for preferredFrameSize of this node from the width and height property of this node. It will return CGSizeZero if width and height dimensions are not of type ASDimensionUnitPoints. - * - * @deprecated Deprecated in version 2.0: Just calls through to set the height and width property of the node. Convert to use sizing properties instead: height, minHeight, maxHeight, width, minWidth, maxWidth. - */ -@property (nonatomic, assign, readwrite) CGSize preferredFrameSize ASDISPLAYNODE_DEPRECATED_MSG("Use .style.preferredSize instead OR set individual values with .style.height and .style.width."); - -/** - * @abstract Asks the node to measure and return the size that best fits its subnodes. - * - * @param constrainedSize The maximum size the receiver should fit in. - * - * @return A new size that fits the receiver's subviews. - * - * @discussion Though this method does not set the bounds of the view, it does have side effects--caching both the - * constraint and the result. - * - * @warning Subclasses must not override this; it calls -measureWithSizeRange: with zero min size. - * -measureWithSizeRange: caches results from -calculateLayoutThatFits:. Calling this method may - * be expensive if result is not cached. - * - * @see measureWithSizeRange: - * @see [ASDisplayNode(Subclassing) calculateLayoutThatFits:] - * - * @deprecated Deprecated in version 2.0: Use layoutThatFits: with a constrained size of (CGSizeZero, constrainedSize) and call size on the returned ASLayout - */ -- (CGSize)measure:(CGSize)constrainedSize/* ASDISPLAYNODE_DEPRECATED_MSG("Use layoutThatFits: with a constrained size of (CGSizeZero, constrainedSize) and call size on the returned ASLayout.")*/; - -ASLayoutElementStyleForwardingDeclaration - -/** - * @abstract Called whenever the visiblity of the node changed. - * - * @discussion Subclasses may use this to monitor when they become visible. - * - * @deprecated @see didEnterVisibleState @see didExitVisibleState - */ -- (void)visibilityDidChange:(BOOL)isVisible ASDISPLAYNODE_REQUIRES_SUPER ASDISPLAYNODE_DEPRECATED_MSG("Use -didEnterVisibleState / -didExitVisibleState instead."); - -/** - * @abstract Called whenever the visiblity of the node changed. - * - * @discussion Subclasses may use this to monitor when they become visible. - * - * @deprecated @see didEnterVisibleState @see didExitVisibleState - */ -- (void)visibleStateDidChange:(BOOL)isVisible ASDISPLAYNODE_REQUIRES_SUPER ASDISPLAYNODE_DEPRECATED_MSG("Use -didEnterVisibleState / -didExitVisibleState instead."); - -/** - * @abstract Called whenever the the node has entered or exited the display state. - * - * @discussion Subclasses may use this to monitor when a node should be rendering its content. - * - * @note This method can be called from any thread and should therefore be thread safe. - * - * @deprecated @see didEnterDisplayState @see didExitDisplayState - */ -- (void)displayStateDidChange:(BOOL)inDisplayState ASDISPLAYNODE_REQUIRES_SUPER ASDISPLAYNODE_DEPRECATED_MSG("Use -didEnterDisplayState / -didExitDisplayState instead."); - -/** - * @abstract Called whenever the the node has entered or left the load state. - * - * @discussion Subclasses may use this to monitor data for a node should be loaded, either from a local or remote source. - * - * @note This method can be called from any thread and should therefore be thread safe. - * - * @deprecated @see didEnterPreloadState @see didExitPreloadState - */ -- (void)loadStateDidChange:(BOOL)inLoadState ASDISPLAYNODE_REQUIRES_SUPER ASDISPLAYNODE_DEPRECATED_MSG("Use -didEnterPreloadState / -didExitPreloadState instead."); - -/** - * @abstract Cancels all performing layout transitions. Can be called on any thread. - * - * @deprecated Deprecated in version 2.0: Use cancelLayoutTransition - */ -- (void)cancelLayoutTransitionsInProgress ASDISPLAYNODE_DEPRECATED_MSG("Use -cancelLayoutTransition instead."); - -/** - * @abstract A boolean that shows whether the node automatically inserts and removes nodes based on the presence or - * absence of the node and its subnodes is completely determined in its layoutSpecThatFits: method. - * - * @discussion If flag is YES the node no longer require addSubnode: or removeFromSupernode method calls. The presence - * or absence of subnodes is completely determined in its layoutSpecThatFits: method. - * - * @deprecated Deprecated in version 2.0: Use automaticallyManagesSubnodes - */ -@property (nonatomic, assign) BOOL usesImplicitHierarchyManagement ASDISPLAYNODE_DEPRECATED_MSG("Set .automaticallyManagesSubnodes instead."); - -/** - * @abstract Indicates that the node should fetch any external data, such as images. - * - * @discussion Subclasses may override this method to be notified when they should begin to preload. Fetching - * should be done asynchronously. The node is also responsible for managing the memory of any data. - * The data may be remote and accessed via the network, but could also be a local database query. - */ -- (void)fetchData ASDISPLAYNODE_REQUIRES_SUPER ASDISPLAYNODE_DEPRECATED_MSG("Use -didEnterPreloadState instead."); - -/** - * Provides an opportunity to clear any fetched data (e.g. remote / network or database-queried) on the current node. - * - * @discussion This will not clear data recursively for all subnodes. Either call -recursivelyClearPreloadedData or - * selectively clear fetched data. - */ -- (void)clearFetchedData ASDISPLAYNODE_REQUIRES_SUPER ASDISPLAYNODE_DEPRECATED_MSG("Use -didExitPreloadState instead."); - -@end diff --git a/submodules/AsyncDisplayKit/Source/ASDisplayNode+Layout.mm b/submodules/AsyncDisplayKit/Source/ASDisplayNode+Layout.mm deleted file mode 100644 index 32934a599c..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASDisplayNode+Layout.mm +++ /dev/null @@ -1,1036 +0,0 @@ -// -// ASDisplayNode+Layout.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import -#import -#import "ASDisplayNodeInternal.h" -#import -#import -#import -#import -#import "ASLayoutElementStylePrivate.h" -#import - -using AS::MutexLocker; - -#pragma mark - ASDisplayNode (ASLayoutElement) - -@implementation ASDisplayNode (ASLayoutElement) - -#pragma mark - -- (BOOL)implementsLayoutMethod -{ - MutexLocker l(__instanceLock__); - return (_methodOverrides & (ASDisplayNodeMethodOverrideLayoutSpecThatFits | - ASDisplayNodeMethodOverrideCalcLayoutThatFits | - ASDisplayNodeMethodOverrideCalcSizeThatFits)) != 0 || _layoutSpecBlock != nil; -} - - -- (ASLayoutElementStyle *)style -{ - MutexLocker l(__instanceLock__); - return [self _locked_style]; -} - -- (ASLayoutElementStyle *)_locked_style -{ - if (_style == nil) { - _style = [[ASLayoutElementStyle alloc] init]; - } - return _style; -} - -- (ASLayoutElementType)layoutElementType -{ - return ASLayoutElementTypeDisplayNode; -} - -- (NSArray> *)sublayoutElements -{ - return self.subnodes; -} - -#pragma mark Measurement Pass - -- (ASLayout *)layoutThatFits:(ASSizeRange)constrainedSize -{ - return [self layoutThatFits:constrainedSize parentSize:constrainedSize.max]; -} - -- (CGSize)measure:(CGSize)constrainedSize { - return [self layoutThatFits:ASSizeRangeMake(CGSizeZero, constrainedSize)].size; -} - -- (ASLayout *)layoutThatFits:(ASSizeRange)constrainedSize parentSize:(CGSize)parentSize -{ - ASScopedLockSelfOrToRoot(); - - // If one or multiple layout transitions are in flight it still can happen that layout information is requested - // on other threads. As the pending and calculated layout to be updated in the layout transition in here just a - // layout calculation wil be performed without side effect - if ([self _isLayoutTransitionInvalid]) { - return [self calculateLayoutThatFits:constrainedSize restrictedToSize:self.style.size relativeToParentSize:parentSize]; - } - - ASLayout *layout = nil; - NSUInteger version = _layoutVersion; - if (_calculatedDisplayNodeLayout.isValid(constrainedSize, parentSize, version)) { - ASDisplayNodeAssertNotNil(_calculatedDisplayNodeLayout.layout, @"-[ASDisplayNode layoutThatFits:parentSize:] _calculatedDisplayNodeLayout.layout should not be nil! %@", self); - layout = _calculatedDisplayNodeLayout.layout; - } else if (_pendingDisplayNodeLayout.isValid(constrainedSize, parentSize, version)) { - ASDisplayNodeAssertNotNil(_pendingDisplayNodeLayout.layout, @"-[ASDisplayNode layoutThatFits:parentSize:] _pendingDisplayNodeLayout.layout should not be nil! %@", self); - layout = _pendingDisplayNodeLayout.layout; - } else { - // Create a pending display node layout for the layout pass - layout = [self calculateLayoutThatFits:constrainedSize - restrictedToSize:self.style.size - relativeToParentSize:parentSize]; - _pendingDisplayNodeLayout = ASDisplayNodeLayout(layout, constrainedSize, parentSize,version); - ASDisplayNodeAssertNotNil(layout, @"-[ASDisplayNode layoutThatFits:parentSize:] newly calculated layout should not be nil! %@", self); - } - - return layout ?: [ASLayout layoutWithLayoutElement:self size:{0, 0}]; -} - -#pragma mark ASLayoutElementStyleExtensibility - -ASLayoutElementStyleExtensibilityForwarding - -#pragma mark ASPrimitiveTraitCollection - -- (ASPrimitiveTraitCollection)primitiveTraitCollection -{ - return _primitiveTraitCollection.load(); -} - -- (void)setPrimitiveTraitCollection:(ASPrimitiveTraitCollection)traitCollection -{ - if (ASPrimitiveTraitCollectionIsEqualToASPrimitiveTraitCollection(traitCollection, _primitiveTraitCollection.load()) == NO) { - _primitiveTraitCollection = traitCollection; - ASDisplayNodeLogEvent(self, @"asyncTraitCollectionDidChange: %@", NSStringFromASPrimitiveTraitCollection(traitCollection)); - - [self asyncTraitCollectionDidChange]; - } -} - -- (ASTraitCollection *)asyncTraitCollection -{ - return [ASTraitCollection traitCollectionWithASPrimitiveTraitCollection:self.primitiveTraitCollection]; -} - -#pragma mark - ASLayoutElementAsciiArtProtocol - -- (NSString *)asciiArtString -{ - return [ASLayoutSpec asciiArtStringForChildren:@[] parentName:[self asciiArtName]]; -} - -- (NSString *)asciiArtName -{ - NSMutableString *result = [NSMutableString stringWithCString:object_getClassName(self) encoding:NSASCIIStringEncoding]; - if (_debugName) { - [result appendFormat:@" (%@)", _debugName]; - } - return result; -} - -@end - -#pragma mark - -#pragma mark - ASDisplayNode (ASLayout) - -@implementation ASDisplayNode (ASLayout) - -- (ASLayoutEngineType)layoutEngineType -{ -#if YOGA - MutexLocker l(__instanceLock__); - YGNodeRef yogaNode = _style.yogaNode; - BOOL hasYogaParent = (_yogaParent != nil); - BOOL hasYogaChildren = (_yogaChildren.count > 0); - if (yogaNode != NULL && (hasYogaParent || hasYogaChildren)) { - return ASLayoutEngineTypeYoga; - } -#endif - - return ASLayoutEngineTypeLayoutSpec; -} - -- (ASLayout *)calculatedLayout -{ - MutexLocker l(__instanceLock__); - return _calculatedDisplayNodeLayout.layout; -} - -- (CGSize)calculatedSize -{ - MutexLocker l(__instanceLock__); - if (_pendingDisplayNodeLayout.isValid(_layoutVersion)) { - return _pendingDisplayNodeLayout.layout.size; - } - return _calculatedDisplayNodeLayout.layout.size; -} - -- (ASSizeRange)constrainedSizeForCalculatedLayout -{ - MutexLocker l(__instanceLock__); - return [self _locked_constrainedSizeForCalculatedLayout]; -} - -- (ASSizeRange)_locked_constrainedSizeForCalculatedLayout -{ - ASAssertLocked(__instanceLock__); - if (_pendingDisplayNodeLayout.isValid(_layoutVersion)) { - return _pendingDisplayNodeLayout.constrainedSize; - } - return _calculatedDisplayNodeLayout.constrainedSize; -} - -@end - -#pragma mark - -#pragma mark - ASDisplayNode (ASLayoutElementStylability) - -@implementation ASDisplayNode (ASLayoutElementStylability) - -- (instancetype)styledWithBlock:(AS_NOESCAPE void (^)(__kindof ASLayoutElementStyle *style))styleBlock -{ - styleBlock(self.style); - return self; -} - -@end - -#pragma mark - -#pragma mark - ASDisplayNode (ASLayoutInternal) - -@implementation ASDisplayNode (ASLayoutInternal) - -/** - * @abstract Informs the root node that the intrinsic size of the receiver is no longer valid. - * - * @discussion The size of a root node is determined by each subnode. Calling invalidateSize will let the root node know - * that the intrinsic size of the receiver node is no longer valid and a resizing of the root node needs to happen. - */ -- (void)_u_setNeedsLayoutFromAbove -{ - ASDisplayNodeAssertThreadAffinity(self); - ASAssertUnlocked(__instanceLock__); - - // Mark the node for layout in the next layout pass - [self setNeedsLayout]; - - __instanceLock__.lock(); - // Escalate to the root; entire tree must allow adjustments so the layout fits the new child. - // Much of the layout will be re-used as cached (e.g. other items in an unconstrained stack) - ASDisplayNode *supernode = _supernode; - __instanceLock__.unlock(); - - if (supernode) { - // Threading model requires that we unlock before calling a method on our parent. - [supernode _u_setNeedsLayoutFromAbove]; - } else { - // Let the root node method know that the size was invalidated - [self _rootNodeDidInvalidateSize]; - } -} - -// TODO It would be easier to work with if we could `ASAssertUnlocked` here, but we -// cannot due to locking to root in `_u_measureNodeWithBoundsIfNecessary`. -- (void)_rootNodeDidInvalidateSize -{ - ASDisplayNodeAssertThreadAffinity(self); - __instanceLock__.lock(); - - // We are the root node and need to re-flow the layout; at least one child needs a new size. - CGSize boundsSizeForLayout = ASCeilSizeValues(self.bounds.size); - - // Figure out constrainedSize to use - ASSizeRange constrainedSize = ASSizeRangeMake(boundsSizeForLayout); - if (_pendingDisplayNodeLayout.layout != nil) { - constrainedSize = _pendingDisplayNodeLayout.constrainedSize; - } else if (_calculatedDisplayNodeLayout.layout != nil) { - constrainedSize = _calculatedDisplayNodeLayout.constrainedSize; - } - - __instanceLock__.unlock(); - - // Perform a measurement pass to get the full tree layout, adapting to the child's new size. - ASLayout *layout = [self layoutThatFits:constrainedSize]; - - // Check if the returned layout has a different size than our current bounds. - if (CGSizeEqualToSize(boundsSizeForLayout, layout.size) == NO) { - // If so, inform our container we need an update (e.g Table, Collection, ViewController, etc). - [self displayNodeDidInvalidateSizeNewSize:layout.size]; - } -} - -// TODO -// We should remove this logic, which is relatively new, and instead -// rely on the parent / host of the root node to do this size change. That's always been the -// expectation with other node containers like ASTableView, ASCollectionView, ASViewController, etc. -// E.g. in ASCellNode the _interactionDelegate is a Table or Collection that will resize in this -// case. By resizing without participating with the parent, we could get cases where our parent size -// does not match, especially if there is a size constraint that is applied at that level. -// -// In general a node should never need to set its own size, instead allowing its parent to do so - -// even in the root case. Anyhow this is a separate / pre-existing issue, but I think it could be -// causing real issues in cases of resizing nodes. -- (void)displayNodeDidInvalidateSizeNewSize:(CGSize)size -{ - ASDisplayNodeAssertThreadAffinity(self); - - // The default implementation of display node changes the size of itself to the new size - CGRect oldBounds = self.bounds; - CGSize oldSize = oldBounds.size; - CGSize newSize = size; - - if (! CGSizeEqualToSize(oldSize, newSize)) { - self.bounds = (CGRect){ oldBounds.origin, newSize }; - - // Frame's origin must be preserved. Since it is computed from bounds size, anchorPoint - // and position (see frame setter in ASDisplayNode+UIViewBridge), position needs to be adjusted. - CGPoint anchorPoint = self.anchorPoint; - CGPoint oldPosition = self.position; - CGFloat xDelta = (newSize.width - oldSize.width) * anchorPoint.x; - CGFloat yDelta = (newSize.height - oldSize.height) * anchorPoint.y; - self.position = CGPointMake(oldPosition.x + xDelta, oldPosition.y + yDelta); - } -} - -- (void)_u_measureNodeWithBoundsIfNecessary:(CGRect)bounds -{ - // ASAssertUnlocked(__instanceLock__); - ASScopedLockSelfOrToRoot(); - - // Check if we are a subnode in a layout transition. - // In this case no measurement is needed as it's part of the layout transition - if ([self _locked_isLayoutTransitionInvalid]) { - return; - } - - CGSize boundsSizeForLayout = ASCeilSizeValues(bounds.size); - - // Prefer a newer and not yet applied _pendingDisplayNodeLayout over _calculatedDisplayNodeLayout - // If there is no such _pending, check if _calculated is valid to reuse (avoiding recalculation below). - BOOL pendingLayoutIsPreferred = NO; - if (_pendingDisplayNodeLayout.isValid(_layoutVersion)) { - NSUInteger calculatedVersion = _calculatedDisplayNodeLayout.version; - NSUInteger pendingVersion = _pendingDisplayNodeLayout.version; - if (pendingVersion > calculatedVersion) { - pendingLayoutIsPreferred = YES; // Newer _pending - } else if (pendingVersion == calculatedVersion - && !ASSizeRangeEqualToSizeRange(_pendingDisplayNodeLayout.constrainedSize, - _calculatedDisplayNodeLayout.constrainedSize)) { - pendingLayoutIsPreferred = YES; // _pending with a different constrained size - } - } - BOOL calculatedLayoutIsReusable = (_calculatedDisplayNodeLayout.isValid(_layoutVersion) - && (_calculatedDisplayNodeLayout.requestedLayoutFromAbove - || CGSizeEqualToSize(_calculatedDisplayNodeLayout.layout.size, boundsSizeForLayout))); - if (!pendingLayoutIsPreferred && calculatedLayoutIsReusable) { - return; - } - // _calculatedDisplayNodeLayout is not reusable we need to transition to a new one - [self cancelLayoutTransition]; - - BOOL didCreateNewContext = NO; - ASLayoutElementContext *context = ASLayoutElementGetCurrentContext(); - if (context == nil) { - context = [[ASLayoutElementContext alloc] init]; - ASLayoutElementPushContext(context); - didCreateNewContext = YES; - } - - // Figure out previous and pending layouts for layout transition - ASDisplayNodeLayout nextLayout = _pendingDisplayNodeLayout; - #define layoutSizeDifferentFromBounds !CGSizeEqualToSize(nextLayout.layout.size, boundsSizeForLayout) - - // nextLayout was likely created by a call to layoutThatFits:, check if it is valid and can be applied. - // If our bounds size is different than it, or invalid, recalculate. Use #define to avoid nullptr-> - BOOL pendingLayoutApplicable = NO; - if (nextLayout.layout == nil) { - } else if (!nextLayout.isValid(_layoutVersion)) { - } else if (layoutSizeDifferentFromBounds) { - } else { - pendingLayoutApplicable = YES; - } - - if (!pendingLayoutApplicable) { - // Use the last known constrainedSize passed from a parent during layout (if never, use bounds). - NSUInteger version = _layoutVersion; - ASSizeRange constrainedSize = [self _locked_constrainedSizeForLayoutPass]; - ASLayout *layout = [self calculateLayoutThatFits:constrainedSize - restrictedToSize:self.style.size - relativeToParentSize:boundsSizeForLayout]; - nextLayout = ASDisplayNodeLayout(layout, constrainedSize, boundsSizeForLayout, version); - // Now that the constrained size of pending layout might have been reused, the layout is useless - // Release it and any orphaned subnodes it retains - _pendingDisplayNodeLayout.layout = nil; - } - - if (didCreateNewContext) { - ASLayoutElementPopContext(); - } - - // If our new layout's desired size for self doesn't match current size, ask our parent to update it. - // This can occur for either pre-calculated or newly-calculated layouts. - if (nextLayout.requestedLayoutFromAbove == NO - && CGSizeEqualToSize(boundsSizeForLayout, nextLayout.layout.size) == NO) { - // The layout that we have specifies that this node (self) would like to be a different size - // than it currently is. Because that size has been computed within the constrainedSize, we - // expect that calling setNeedsLayoutFromAbove will result in our parent resizing us to this. - // However, in some cases apps may manually interfere with this (setting a different bounds). - // In this case, we need to detect that we've already asked to be resized to match this - // particular ASLayout object, and shouldn't loop asking again unless we have a different ASLayout. - nextLayout.requestedLayoutFromAbove = YES; - - { - __instanceLock__.unlock(); - [self _u_setNeedsLayoutFromAbove]; - __instanceLock__.lock(); - } - - // Update the layout's version here because _u_setNeedsLayoutFromAbove calls __setNeedsLayout which in turn increases _layoutVersion - // Failing to do this will cause the layout to be invalid immediately - nextLayout.version = _layoutVersion; - } - - // Prepare to transition to nextLayout - ASDisplayNodeAssertNotNil(nextLayout.layout, @"nextLayout.layout should not be nil! %@", self); - _pendingLayoutTransition = [[ASLayoutTransition alloc] initWithNode:self - pendingLayout:nextLayout - previousLayout:_calculatedDisplayNodeLayout]; - - // If a parent is currently executing a layout transition, perform our layout application after it. - if (ASHierarchyStateIncludesLayoutPending(_hierarchyState) == NO) { - // If no transition, apply our new layout immediately (common case). - [self _completePendingLayoutTransition]; - } -} - -- (ASSizeRange)_constrainedSizeForLayoutPass -{ - MutexLocker l(__instanceLock__); - return [self _locked_constrainedSizeForLayoutPass]; -} - -- (ASSizeRange)_locked_constrainedSizeForLayoutPass -{ - // TODO: The logic in -_u_setNeedsLayoutFromAbove seems correct and doesn't use this method. - // logic seems correct. For what case does -this method need to do the CGSizeEqual checks? - // IF WE CAN REMOVE BOUNDS CHECKS HERE, THEN WE CAN ALSO REMOVE "REQUESTED FROM ABOVE" CHECK - - ASAssertLocked(__instanceLock__); - - CGSize boundsSizeForLayout = ASCeilSizeValues(self.threadSafeBounds.size); - - // Checkout if constrained size of pending or calculated display node layout can be used - if (_pendingDisplayNodeLayout.requestedLayoutFromAbove - || CGSizeEqualToSize(_pendingDisplayNodeLayout.layout.size, boundsSizeForLayout)) { - // We assume the size from the last returned layoutThatFits: layout was applied so use the pending display node - // layout constrained size - return _pendingDisplayNodeLayout.constrainedSize; - } else if (_calculatedDisplayNodeLayout.layout != nil - && (_calculatedDisplayNodeLayout.requestedLayoutFromAbove - || CGSizeEqualToSize(_calculatedDisplayNodeLayout.layout.size, boundsSizeForLayout))) { - // We assume the _calculatedDisplayNodeLayout is still valid and the frame is not different - return _calculatedDisplayNodeLayout.constrainedSize; - } else { - // In this case neither the _pendingDisplayNodeLayout or the _calculatedDisplayNodeLayout constrained size can - // be reused, so the current bounds is used. This is usual the case if a frame was set manually that differs to - // the one returned from layoutThatFits: or layoutThatFits: was never called - return ASSizeRangeMake(boundsSizeForLayout); - } -} - -- (void)_layoutSublayouts -{ - ASDisplayNodeAssertThreadAffinity(self); - // ASAssertUnlocked(__instanceLock__); - - ASLayout *layout; - { - MutexLocker l(__instanceLock__); - if (_calculatedDisplayNodeLayout.version < _layoutVersion) { - return; - } - layout = _calculatedDisplayNodeLayout.layout; - } - - for (ASDisplayNode *node in self.subnodes) { - CGRect frame = [layout frameForElement:node]; - if (CGRectIsNull(frame)) { - // There is no frame for this node in our layout. - // This currently can happen if we get a CA layout pass - // while waiting for the client to run animateLayoutTransition: - } else { - node.frame = frame; - } - } -} - -@end - -#pragma mark - -#pragma mark - ASDisplayNode (ASAutomatic Subnode Management) - -@implementation ASDisplayNode (ASAutomaticSubnodeManagement) - -#pragma mark Automatically Manages Subnodes - -- (BOOL)automaticallyManagesSubnodes -{ - MutexLocker l(__instanceLock__); - return _automaticallyManagesSubnodes; -} - -- (void)setAutomaticallyManagesSubnodes:(BOOL)automaticallyManagesSubnodes -{ - MutexLocker l(__instanceLock__); - _automaticallyManagesSubnodes = automaticallyManagesSubnodes; -} - -@end - -#pragma mark - -#pragma mark - ASDisplayNode (ASLayoutTransition) - -@implementation ASDisplayNode (ASLayoutTransition) - -- (BOOL)_isLayoutTransitionInvalid -{ - MutexLocker l(__instanceLock__); - return [self _locked_isLayoutTransitionInvalid]; -} - -- (BOOL)_locked_isLayoutTransitionInvalid -{ - ASAssertLocked(__instanceLock__); - if (ASHierarchyStateIncludesLayoutPending(_hierarchyState)) { - ASLayoutElementContext *context = ASLayoutElementGetCurrentContext(); - if (context == nil || _pendingTransitionID != context.transitionID) { - return YES; - } - } - return NO; -} - -/// Starts a new transition and returns the transition id -- (int32_t)_startNewTransition -{ - static std::atomic gNextTransitionID; - int32_t newTransitionID = gNextTransitionID.fetch_add(1) + 1; - _transitionID = newTransitionID; - return newTransitionID; -} - -/// Returns NO if there was no transition to cancel/finish. -- (BOOL)_finishOrCancelTransition -{ - int32_t oldValue = _transitionID.exchange(ASLayoutElementContextInvalidTransitionID); - return oldValue != ASLayoutElementContextInvalidTransitionID; -} - -#pragma mark Layout Transition - -- (void)transitionLayoutWithAnimation:(BOOL)animated - shouldMeasureAsync:(BOOL)shouldMeasureAsync - measurementCompletion:(void(^)())completion -{ - ASDisplayNodeAssertMainThread(); - [self transitionLayoutWithSizeRange:[self _constrainedSizeForLayoutPass] - animated:animated - shouldMeasureAsync:shouldMeasureAsync - measurementCompletion:completion]; -} - -- (void)transitionLayoutWithSizeRange:(ASSizeRange)constrainedSize - animated:(BOOL)animated - shouldMeasureAsync:(BOOL)shouldMeasureAsync - measurementCompletion:(void(^)())completion -{ - ASDisplayNodeAssertMainThread(); - - if (constrainedSize.max.width <= 0.0 || constrainedSize.max.height <= 0.0) { - // Using CGSizeZero for the sizeRange can cause negative values in client layout code. - // Most likely called transitionLayout: without providing a size, before first layout pass. - return; - } - - { - MutexLocker l(__instanceLock__); - - // Check if we are a subnode in a layout transition. - // In this case no measurement is needed as we're part of the layout transition. - if ([self _locked_isLayoutTransitionInvalid]) { - return; - } - - if (ASHierarchyStateIncludesLayoutPending(_hierarchyState)) { - ASDisplayNodeAssert(NO, @"Can't start a transition when one of the supernodes is performing one."); - return; - } - } - - // Invalidate calculated layout because this method acts as an animated "setNeedsLayout" for nodes. - // If the user has reconfigured the node and calls this, we should never return a stale layout - // for subsequent calls to layoutThatFits: regardless of size range. We choose this method rather than - // -setNeedsLayout because that method also triggers a CA layout invalidation, which isn't necessary at this time. - // See https://github.com/TextureGroup/Texture/issues/463 - [self invalidateCalculatedLayout]; - - // Every new layout transition has a transition id associated to check in subsequent transitions for cancelling - int32_t transitionID = [self _startNewTransition]; - // NOTE: This block captures self. It's cheaper than hitting the weak table. - asdisplaynode_iscancelled_block_t isCancelled = ^{ - BOOL result = (_transitionID != transitionID); - if (result) { - } - return result; - }; - - // Move all subnodes in layout pending state for this transition - ASDisplayNodePerformBlockOnEverySubnode(self, NO, ^(ASDisplayNode * _Nonnull node) { - ASDisplayNodeAssert(node->_transitionID == ASLayoutElementContextInvalidTransitionID, @"Can't start a transition when one of the subnodes is performing one."); - node.hierarchyState |= ASHierarchyStateLayoutPending; - node->_pendingTransitionID = transitionID; - }); - - // Transition block that executes the layout transition - void (^transitionBlock)(void) = ^{ - if (isCancelled()) { - return; - } - - // Perform a full layout creation pass with passed in constrained size to create the new layout for the transition - NSUInteger newLayoutVersion = _layoutVersion; - ASLayout *newLayout; - { - ASScopedLockSelfOrToRoot(); - - ASLayoutElementContext *ctx = [[ASLayoutElementContext alloc] init]; - ctx.transitionID = transitionID; - ASLayoutElementPushContext(ctx); - - BOOL automaticallyManagesSubnodesDisabled = (self.automaticallyManagesSubnodes == NO); - self.automaticallyManagesSubnodes = YES; // Temporary flag for 1.9.x - newLayout = [self calculateLayoutThatFits:constrainedSize - restrictedToSize:self.style.size - relativeToParentSize:constrainedSize.max]; - if (automaticallyManagesSubnodesDisabled) { - self.automaticallyManagesSubnodes = NO; // Temporary flag for 1.9.x - } - - ASLayoutElementPopContext(); - } - - if (isCancelled()) { - return; - } - - ASPerformBlockOnMainThread(^{ - if (isCancelled()) { - return; - } - ASLayoutTransition *pendingLayoutTransition; - _ASTransitionContext *pendingLayoutTransitionContext; - { - // Grab __instanceLock__ here to make sure this transition isn't invalidated - // right after it passed the validation test and before it proceeds - MutexLocker l(__instanceLock__); - - // Update calculated layout - const auto previousLayout = _calculatedDisplayNodeLayout; - const auto pendingLayout = ASDisplayNodeLayout(newLayout, - constrainedSize, - constrainedSize.max, - newLayoutVersion); - [self _locked_setCalculatedDisplayNodeLayout:pendingLayout]; - - // Setup pending layout transition for animation - _pendingLayoutTransition = pendingLayoutTransition = [[ASLayoutTransition alloc] initWithNode:self - pendingLayout:pendingLayout - previousLayout:previousLayout]; - // Setup context for pending layout transition. we need to hold a strong reference to the context - _pendingLayoutTransitionContext = pendingLayoutTransitionContext = [[_ASTransitionContext alloc] initWithAnimation:animated - layoutDelegate:_pendingLayoutTransition - completionDelegate:self]; - } - - // Apply complete layout transitions for all subnodes - { - ASDisplayNodePerformBlockOnEverySubnode(self, NO, ^(ASDisplayNode * _Nonnull node) { - [node _completePendingLayoutTransition]; - node.hierarchyState &= (~ASHierarchyStateLayoutPending); - }); - } - - // Measurement pass completion - // Give the subclass a change to hook into before calling the completion block - [self _layoutTransitionMeasurementDidFinish]; - if (completion) { - completion(); - } - - // Apply the subnode insertion immediately to be able to animate the nodes - [pendingLayoutTransition applySubnodeInsertionsAndMoves]; - - // Kick off animating the layout transition - { - [self animateLayoutTransition:pendingLayoutTransitionContext]; - } - - // Mark transaction as finished - [self _finishOrCancelTransition]; - }); - }; - - // Start transition based on flag on current or background thread - if (shouldMeasureAsync) { - ASPerformBlockOnBackgroundThread(transitionBlock); - } else { - transitionBlock(); - } -} - -- (void)cancelLayoutTransition -{ - if ([self _finishOrCancelTransition]) { - // Tell subnodes to exit layout pending state and clear related properties - ASDisplayNodePerformBlockOnEverySubnode(self, NO, ^(ASDisplayNode * _Nonnull node) { - node.hierarchyState &= (~ASHierarchyStateLayoutPending); - }); - } -} - -- (void)setDefaultLayoutTransitionDuration:(NSTimeInterval)defaultLayoutTransitionDuration -{ - MutexLocker l(__instanceLock__); - _defaultLayoutTransitionDuration = defaultLayoutTransitionDuration; -} - -- (NSTimeInterval)defaultLayoutTransitionDuration -{ - MutexLocker l(__instanceLock__); - return _defaultLayoutTransitionDuration; -} - -- (void)setDefaultLayoutTransitionDelay:(NSTimeInterval)defaultLayoutTransitionDelay -{ - MutexLocker l(__instanceLock__); - _defaultLayoutTransitionDelay = defaultLayoutTransitionDelay; -} - -- (NSTimeInterval)defaultLayoutTransitionDelay -{ - MutexLocker l(__instanceLock__); - return _defaultLayoutTransitionDelay; -} - -- (void)setDefaultLayoutTransitionOptions:(UIViewAnimationOptions)defaultLayoutTransitionOptions -{ - MutexLocker l(__instanceLock__); - _defaultLayoutTransitionOptions = defaultLayoutTransitionOptions; -} - -- (UIViewAnimationOptions)defaultLayoutTransitionOptions -{ - MutexLocker l(__instanceLock__); - return _defaultLayoutTransitionOptions; -} - -#pragma mark - -/* - * Hook for subclasses to perform an animation based on the given ASContextTransitioning. By default a fade in and out - * animation is provided. - */ -- (void)animateLayoutTransition:(id)context -{ - if ([context isAnimated] == NO) { - [self _layoutSublayouts]; - [context completeTransition:YES]; - return; - } - - ASDisplayNode *node = self; - - NSAssert(node.isNodeLoaded == YES, @"Invalid node state"); - - NSArray *removedSubnodes = [context removedSubnodes]; - NSMutableArray *insertedSubnodes = [[context insertedSubnodes] mutableCopy]; - const auto movedSubnodes = [[NSMutableArray alloc] init]; - - const auto insertedSubnodeContexts = [[NSMutableArray<_ASAnimatedTransitionContext *> alloc] init]; - const auto removedSubnodeContexts = [[NSMutableArray<_ASAnimatedTransitionContext *> alloc] init]; - - for (ASDisplayNode *subnode in [context subnodesForKey:ASTransitionContextToLayoutKey]) { - if ([insertedSubnodes containsObject:subnode] == NO) { - // This is an existing subnode, check if it is resized, moved or both - CGRect fromFrame = [context initialFrameForNode:subnode]; - CGRect toFrame = [context finalFrameForNode:subnode]; - if (CGSizeEqualToSize(fromFrame.size, toFrame.size) == NO) { - [insertedSubnodes addObject:subnode]; - } - if (CGPointEqualToPoint(fromFrame.origin, toFrame.origin) == NO) { - [movedSubnodes addObject:subnode]; - } - } - } - - // Create contexts for inserted and removed subnodes - for (ASDisplayNode *insertedSubnode in insertedSubnodes) { - [insertedSubnodeContexts addObject:[_ASAnimatedTransitionContext contextForNode:insertedSubnode alpha:insertedSubnode.alpha]]; - } - for (ASDisplayNode *removedSubnode in removedSubnodes) { - [removedSubnodeContexts addObject:[_ASAnimatedTransitionContext contextForNode:removedSubnode alpha:removedSubnode.alpha]]; - } - - // Fade out inserted subnodes - for (ASDisplayNode *insertedSubnode in insertedSubnodes) { - insertedSubnode.frame = [context finalFrameForNode:insertedSubnode]; - insertedSubnode.alpha = 0; - } - - // Adjust groupOpacity for animation - BOOL originAllowsGroupOpacity = node.allowsGroupOpacity; - node.allowsGroupOpacity = YES; - - [UIView animateWithDuration:self.defaultLayoutTransitionDuration delay:self.defaultLayoutTransitionDelay options:self.defaultLayoutTransitionOptions animations:^{ - // Fade removed subnodes and views out - for (ASDisplayNode *removedSubnode in removedSubnodes) { - removedSubnode.alpha = 0; - } - - // Fade inserted subnodes in - for (_ASAnimatedTransitionContext *insertedSubnodeContext in insertedSubnodeContexts) { - insertedSubnodeContext.node.alpha = insertedSubnodeContext.alpha; - } - - // Update frame of self and moved subnodes - CGSize fromSize = [context layoutForKey:ASTransitionContextFromLayoutKey].size; - CGSize toSize = [context layoutForKey:ASTransitionContextToLayoutKey].size; - BOOL isResized = (CGSizeEqualToSize(fromSize, toSize) == NO); - if (isResized == YES) { - CGPoint position = node.frame.origin; - node.frame = CGRectMake(position.x, position.y, toSize.width, toSize.height); - } - for (ASDisplayNode *movedSubnode in movedSubnodes) { - movedSubnode.frame = [context finalFrameForNode:movedSubnode]; - } - } completion:^(BOOL finished) { - // Restore all removed subnode alpha values - for (_ASAnimatedTransitionContext *removedSubnodeContext in removedSubnodeContexts) { - removedSubnodeContext.node.alpha = removedSubnodeContext.alpha; - } - - // Restore group opacity - node.allowsGroupOpacity = originAllowsGroupOpacity; - - // Subnode removals are automatically performed - [context completeTransition:finished]; - }]; -} - -/** - * Hook for subclasses to clean up nodes after the transition happened. Furthermore this can be used from subclasses - * to manually perform deletions. - */ -- (void)didCompleteLayoutTransition:(id)context -{ - ASDisplayNodeAssertMainThread(); - - __instanceLock__.lock(); - ASLayoutTransition *pendingLayoutTransition = _pendingLayoutTransition; - __instanceLock__.unlock(); - - [pendingLayoutTransition applySubnodeRemovals]; -} - -/** - * Completes the pending layout transition immediately without going through the the Layout Transition Animation API - */ -- (void)_completePendingLayoutTransition -{ - __instanceLock__.lock(); - ASLayoutTransition *pendingLayoutTransition = _pendingLayoutTransition; - __instanceLock__.unlock(); - - if (pendingLayoutTransition != nil) { - [self _setCalculatedDisplayNodeLayout:pendingLayoutTransition.pendingLayout]; - [self _completeLayoutTransition:pendingLayoutTransition]; - [self _pendingLayoutTransitionDidComplete]; - } -} - -/** - * Can be directly called to commit the given layout transition immediately to complete without calling through to the - * Layout Transition Animation API - */ -- (void)_completeLayoutTransition:(ASLayoutTransition *)layoutTransition -{ - // Layout transition is not supported for nodes that do not have automatic subnode management enabled - if (layoutTransition == nil || self.automaticallyManagesSubnodes == NO) { - return; - } - - // Trampoline to the main thread if necessary - if (ASDisplayNodeThreadIsMain() || layoutTransition.isSynchronous == NO) { - // Committing the layout transition will result in subnode insertions and removals, both of which must be called without the lock held - // TODO: Disabled due to PR: https://github.com/TextureGroup/Texture/pull/1204 - // ASAssertUnlocked(__instanceLock__); - [layoutTransition commitTransition]; - } else { - // Subnode insertions and removals need to happen always on the main thread if at least one subnode is already loaded - ASPerformBlockOnMainThread(^{ - [layoutTransition commitTransition]; - }); - } -} - -- (void)_assertSubnodeState -{ - // Verify that any orphaned nodes are removed. - // This can occur in rare cases if main thread layout is flushed while a background layout is calculating. - - if (self.automaticallyManagesSubnodes == NO) { - return; - } - - MutexLocker l(__instanceLock__); - NSArray *sublayouts = _calculatedDisplayNodeLayout.layout.sublayouts; - unowned ASLayout *cSublayouts[sublayouts.count]; - [sublayouts getObjects:cSublayouts range:NSMakeRange(0, AS_ARRAY_SIZE(cSublayouts))]; - - // Fast-path if we are in the correct state (likely). - if (_subnodes.count == AS_ARRAY_SIZE(cSublayouts)) { - NSUInteger i = 0; - BOOL matches = YES; - for (ASDisplayNode *subnode in _subnodes) { - if (subnode != cSublayouts[i].layoutElement) { - matches = NO; - } - i++; - } - if (matches) { - return; - } - } - - NSArray *layoutNodes = ASArrayByFlatMapping(sublayouts, ASLayout *layout, (ASDisplayNode *)layout.layoutElement); - NSIndexSet *insertions, *deletions; - [_subnodes asdk_diffWithArray:layoutNodes insertions:&insertions deletions:&deletions]; - if (insertions.count > 0) { - NSLog(@"Warning: node's layout includes subnode that has not been added: node = %@, subnodes = %@, subnodes in layout = %@", self, _subnodes, layoutNodes); - } - - // Remove any nodes that are in the tree but should not be. - // Go in reverse order so we don't shift our indexes. - if (deletions) { - for (NSUInteger i = deletions.lastIndex; i != NSNotFound; i = [deletions indexLessThanIndex:i]) { - NSLog(@"Automatically removing orphaned subnode %@, from parent %@", _subnodes[i], self); - [_subnodes[i] removeFromSupernode]; - } - } -} - -- (void)_pendingLayoutTransitionDidComplete -{ - // This assertion introduces a breaking behavior for nodes that has ASM enabled but also manually manage some subnodes. - // Let's gate it behind YOGA flag. -#if YOGA - [self _assertSubnodeState]; -#endif - - // Subclass hook - // TODO: Disabled due to PR: https://github.com/TextureGroup/Texture/pull/1204 - // ASAssertUnlocked(__instanceLock__); - [self calculatedLayoutDidChange]; - - // Grab lock after calling out to subclass - MutexLocker l(__instanceLock__); - - // We generate placeholders at -layoutThatFits: time so that a node is guaranteed to have a placeholder ready to go. - // This is also because measurement is usually asynchronous, but placeholders need to be set up synchronously. - // First measurement is guaranteed to be before the node is onscreen, so we can create the image async. but still have it appear sync. - if (_placeholderEnabled && !_placeholderImage && [self _locked_displaysAsynchronously]) { - - // Zero-sized nodes do not require a placeholder. - CGSize layoutSize = _calculatedDisplayNodeLayout.layout.size; - if (layoutSize.width * layoutSize.height <= 0.0) { - return; - } - - // If we've displayed our contents, we don't need a placeholder. - // Contents is a thread-affined property and can't be read off main after loading. - if (self.isNodeLoaded) { - ASPerformBlockOnMainThread(^{ - if (self.contents == nil) { - _placeholderImage = [self placeholderImage]; - } - }); - } else { - if (self.contents == nil) { - _placeholderImage = [self placeholderImage]; - } - } - } - - // Cleanup pending layout transition - _pendingLayoutTransition = nil; -} - -- (void)_setCalculatedDisplayNodeLayout:(const ASDisplayNodeLayout &)displayNodeLayout -{ - MutexLocker l(__instanceLock__); - [self _locked_setCalculatedDisplayNodeLayout:displayNodeLayout]; -} - -- (void)_locked_setCalculatedDisplayNodeLayout:(const ASDisplayNodeLayout &)displayNodeLayout -{ - ASAssertLocked(__instanceLock__); - ASDisplayNodeAssertTrue(displayNodeLayout.layout.layoutElement == self); - ASDisplayNodeAssertTrue(displayNodeLayout.layout.size.width >= 0.0); - ASDisplayNodeAssertTrue(displayNodeLayout.layout.size.height >= 0.0); - - _calculatedDisplayNodeLayout = displayNodeLayout; -} - -@end - -#pragma mark - -#pragma mark - ASDisplayNode (YogaLayout) - -@implementation ASDisplayNode (YogaLayout) - -- (BOOL)locked_shouldLayoutFromYogaRoot { -#if YOGA - YGNodeRef yogaNode = _style.yogaNode; - BOOL hasYogaParent = (_yogaParent != nil); - BOOL hasYogaChildren = (_yogaChildren.count > 0); - BOOL usesYoga = (yogaNode != NULL && (hasYogaParent || hasYogaChildren)); - if (usesYoga) { - if ([self shouldHaveYogaMeasureFunc] == NO) { - return YES; - } else { - return NO; - } - } else { - return NO; - } -#else - return NO; -#endif -} - -@end diff --git a/submodules/AsyncDisplayKit/Source/ASDisplayNode+LayoutSpec.mm b/submodules/AsyncDisplayKit/Source/ASDisplayNode+LayoutSpec.mm deleted file mode 100644 index 132bc666f8..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASDisplayNode+LayoutSpec.mm +++ /dev/null @@ -1,145 +0,0 @@ -// -// ASDisplayNode+LayoutSpec.mm -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import - -#import "_ASScopeTimer.h" -#import "ASDisplayNodeInternal.h" -#import -#import -#import "ASLayoutSpec+Subclasses.h" -#import "ASLayoutSpecPrivate.h" -#import - - -@implementation ASDisplayNode (ASLayoutSpec) - -- (void)setLayoutSpecBlock:(ASLayoutSpecBlock)layoutSpecBlock -{ - // For now there should never be an override of layoutSpecThatFits: and a layoutSpecBlock together. - ASDisplayNodeAssert(!(_methodOverrides & ASDisplayNodeMethodOverrideLayoutSpecThatFits), - @"Nodes with a .layoutSpecBlock must not also implement -layoutSpecThatFits:"); - AS::MutexLocker l(__instanceLock__); - _layoutSpecBlock = layoutSpecBlock; -} - -- (ASLayoutSpecBlock)layoutSpecBlock -{ - AS::MutexLocker l(__instanceLock__); - return _layoutSpecBlock; -} - -- (ASLayout *)calculateLayoutLayoutSpec:(ASSizeRange)constrainedSize -{ - AS::UniqueLock l(__instanceLock__); - - // Manual size calculation via calculateSizeThatFits: - if (_layoutSpecBlock == NULL && (_methodOverrides & ASDisplayNodeMethodOverrideLayoutSpecThatFits) == 0) { - CGSize size = [self calculateSizeThatFits:constrainedSize.max]; - ASDisplayNodeLogEvent(self, @"calculatedSize: %@", NSStringFromCGSize(size)); - return [ASLayout layoutWithLayoutElement:self size:ASSizeRangeClamp(constrainedSize, size) sublayouts:nil]; - } - - // Size calcualtion with layout elements - BOOL measureLayoutSpec = _measurementOptions & ASDisplayNodePerformanceMeasurementOptionLayoutSpec; - if (measureLayoutSpec) { - _layoutSpecNumberOfPasses++; - } - - // Get layout element from the node - id layoutElement = [self _locked_layoutElementThatFits:constrainedSize]; -#if ASEnableVerboseLogging - for (NSString *asciiLine in [[layoutElement asciiArtString] componentsSeparatedByString:@"\n"]) { - as_log_verbose(ASLayoutLog(), "%@", asciiLine); - } -#endif - - - // Certain properties are necessary to set on an element of type ASLayoutSpec - if (layoutElement.layoutElementType == ASLayoutElementTypeLayoutSpec) { - ASLayoutSpec *layoutSpec = (ASLayoutSpec *)layoutElement; - -#if AS_DEDUPE_LAYOUT_SPEC_TREE - NSHashTable *duplicateElements = [layoutSpec findDuplicatedElementsInSubtree]; - if (duplicateElements.count > 0) { - ASDisplayNodeFailAssert(@"Node %@ returned a layout spec that contains the same elements in multiple positions. Elements: %@", self, duplicateElements); - // Use an empty layout spec to avoid crashes - layoutSpec = [[ASLayoutSpec alloc] init]; - } -#endif - - ASDisplayNodeAssert(layoutSpec.isMutable, @"Node %@ returned layout spec %@ that has already been used. Layout specs should always be regenerated.", self, layoutSpec); - - layoutSpec.isMutable = NO; - } - - // Manually propagate the trait collection here so that any layoutSpec children of layoutSpec will get a traitCollection - { - AS::SumScopeTimer t(_layoutSpecTotalTime, measureLayoutSpec); - ASTraitCollectionPropagateDown(layoutElement, self.primitiveTraitCollection); - } - - BOOL measureLayoutComputation = _measurementOptions & ASDisplayNodePerformanceMeasurementOptionLayoutComputation; - if (measureLayoutComputation) { - _layoutComputationNumberOfPasses++; - } - - // Layout element layout creation - ASLayout *layout = ({ - AS::SumScopeTimer t(_layoutComputationTotalTime, measureLayoutComputation); - [layoutElement layoutThatFits:constrainedSize]; - }); - ASDisplayNodeAssertNotNil(layout, @"[ASLayoutElement layoutThatFits:] should never return nil! %@, %@", self, layout); - - // Make sure layoutElementObject of the root layout is `self`, so that the flattened layout will be structurally correct. - BOOL isFinalLayoutElement = (layout.layoutElement != self); - if (isFinalLayoutElement) { - layout.position = CGPointZero; - layout = [ASLayout layoutWithLayoutElement:self size:layout.size sublayouts:@[layout]]; - } - ASDisplayNodeLogEvent(self, @"computedLayout: %@", layout); - - // PR #1157: Reduces accuracy of _unflattenedLayout for debugging/Weaver - if ([ASDisplayNode shouldStoreUnflattenedLayouts]) { - _unflattenedLayout = layout; - } - layout = [layout filteredNodeLayoutTree]; - - return layout; -} - -- (id)_locked_layoutElementThatFits:(ASSizeRange)constrainedSize -{ - ASAssertLocked(__instanceLock__); - - BOOL measureLayoutSpec = _measurementOptions & ASDisplayNodePerformanceMeasurementOptionLayoutSpec; - - if (_layoutSpecBlock != NULL) { - return ({ - AS::MutexLocker l(__instanceLock__); - AS::SumScopeTimer t(_layoutSpecTotalTime, measureLayoutSpec); - _layoutSpecBlock(self, constrainedSize); - }); - } else { - return ({ - AS::SumScopeTimer t(_layoutSpecTotalTime, measureLayoutSpec); - [self layoutSpecThatFits:constrainedSize]; - }); - } -} - -- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize -{ - __ASDisplayNodeCheckForLayoutMethodOverrides; - - ASDisplayNodeAssert(NO, @"-[ASDisplayNode layoutSpecThatFits:] should never return an empty value. One way this is caused is by calling -[super layoutSpecThatFits:] which is not currently supported."); - return [[ASLayoutSpec alloc] init]; -} - -@end diff --git a/submodules/AsyncDisplayKit/Source/ASDisplayNode+UIViewBridge.mm b/submodules/AsyncDisplayKit/Source/ASDisplayNode+UIViewBridge.mm deleted file mode 100644 index 084800a1ed..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASDisplayNode+UIViewBridge.mm +++ /dev/null @@ -1,1325 +0,0 @@ -// -// ASDisplayNode+UIViewBridge.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import "_ASPendingState.h" -#import -#import "ASDisplayNodeInternal.h" -#import -#import -#import "ASPendingStateController.h" - -/** - * The following macros are conveniences to help in the common tasks related to the bridging that ASDisplayNode does to UIView and CALayer. - * In general, a property can either be: - * - Always sent to the layer or view's layer - * use _getFromLayer / _setToLayer - * - Bridged to the view if view-backed or the layer if layer-backed - * use _getFromViewOrLayer / _setToViewOrLayer / _messageToViewOrLayer - * - Only applicable if view-backed - * use _setToViewOnly / _getFromViewOnly - * - Has differing types on views and layers, or custom ASDisplayNode-specific behavior is desired - * manually implement - * - * _bridge_prologue_write is defined to take the node's property lock. Add it at the beginning of any bridged property setters. - * _bridge_prologue_read is defined to take the node's property lock and enforce thread affinity. Add it at the beginning of any bridged property getters. - */ - -#define DISPLAYNODE_USE_LOCKS 1 - -#if DISPLAYNODE_USE_LOCKS -#define _bridge_prologue_read AS::MutexLocker l(__instanceLock__); ASDisplayNodeAssertThreadAffinity(self) -#define _bridge_prologue_write AS::MutexLocker l(__instanceLock__) -#else -#define _bridge_prologue_read ASDisplayNodeAssertThreadAffinity(self) -#define _bridge_prologue_write -#endif - -/// Returns YES if the property set should be applied to view/layer immediately. -/// Side Effect: Registers the node with the shared ASPendingStateController if -/// the property cannot be immediately applied and the node does not already have pending changes. -/// This function must be called with the node's lock already held (after _bridge_prologue_write). -/// *warning* the lock should *not* be released until the pending state is updated if this method -/// returns NO. Otherwise, the pending state can be scheduled and flushed *before* you get a chance -/// to apply it. -ASDISPLAYNODE_INLINE BOOL ASDisplayNodeShouldApplyBridgedWriteToView(ASDisplayNode *node) { - BOOL loaded = _loaded(node); - if (ASDisplayNodeThreadIsMain()) { - return loaded; - } else { - if (loaded && !ASDisplayNodeGetPendingState(node).hasChanges) { - [[ASPendingStateController sharedInstance] registerNode:node]; - } - return NO; - } -}; - -#define _getFromViewOrLayer(layerProperty, viewAndPendingViewStateProperty) _loaded(self) ? \ - (_view ? _view.viewAndPendingViewStateProperty : _layer.layerProperty )\ - : ASDisplayNodeGetPendingState(self).viewAndPendingViewStateProperty - -#define _setToViewOrLayer(layerProperty, layerValueExpr, viewAndPendingViewStateProperty, viewAndPendingViewStateExpr) BOOL shouldApply = ASDisplayNodeShouldApplyBridgedWriteToView(self); \ - if (shouldApply) { (_view ? _view.viewAndPendingViewStateProperty = (viewAndPendingViewStateExpr) : _layer.layerProperty = (layerValueExpr)); } else { ASDisplayNodeGetPendingState(self).viewAndPendingViewStateProperty = (viewAndPendingViewStateExpr); } - -#define _setToViewOnly(viewAndPendingViewStateProperty, viewAndPendingViewStateExpr) BOOL shouldApply = ASDisplayNodeShouldApplyBridgedWriteToView(self); \ -if (shouldApply) { _view.viewAndPendingViewStateProperty = (viewAndPendingViewStateExpr); } else { ASDisplayNodeGetPendingState(self).viewAndPendingViewStateProperty = (viewAndPendingViewStateExpr); } - -#define _getFromViewOnly(viewAndPendingViewStateProperty) _loaded(self) ? _view.viewAndPendingViewStateProperty : ASDisplayNodeGetPendingState(self).viewAndPendingViewStateProperty - -#define _getFromLayer(layerProperty) _loaded(self) ? _layer.layerProperty : ASDisplayNodeGetPendingState(self).layerProperty - -#define _setToLayer(layerProperty, layerValueExpr) BOOL shouldApply = ASDisplayNodeShouldApplyBridgedWriteToView(self); \ -if (shouldApply) { _layer.layerProperty = (layerValueExpr); } else { ASDisplayNodeGetPendingState(self).layerProperty = (layerValueExpr); } - -/** - * This category implements certain frequently-used properties and methods of UIView and CALayer so that ASDisplayNode clients can just call the view/layer methods on the node, - * with minimal loss in performance. Unlike UIView and CALayer methods, these can be called from a non-main thread until the view or layer is created. - * This allows text sizing in -calculateSizeThatFits: (essentially a simplified layout) to happen off the main thread - * without any CALayer or UIView actually existing while still being able to set and read properties from ASDisplayNode instances. - */ -@implementation ASDisplayNode (UIViewBridge) - -#if TARGET_OS_TV -// Focus Engine -- (BOOL)canBecomeFocused -{ - return NO; -} - -- (void)setNeedsFocusUpdate -{ - ASDisplayNodeAssertMainThread(); - [_view setNeedsFocusUpdate]; -} - -- (void)updateFocusIfNeeded -{ - ASDisplayNodeAssertMainThread(); - [_view updateFocusIfNeeded]; -} - -- (BOOL)shouldUpdateFocusInContext:(UIFocusUpdateContext *)context -{ - return NO; -} - -- (void)didUpdateFocusInContext:(UIFocusUpdateContext *)context withAnimationCoordinator:(UIFocusAnimationCoordinator *)coordinator -{ - -} - -- (UIView *)preferredFocusedView -{ - if (self.nodeLoaded) { - return _view; - } - else { - return nil; - } -} -#endif - -- (BOOL)canBecomeFirstResponder -{ - ASDisplayNodeAssertMainThread(); - return [self __canBecomeFirstResponder]; -} - -- (BOOL)canResignFirstResponder -{ - ASDisplayNodeAssertMainThread(); - return [self __canResignFirstResponder]; -} - -- (BOOL)isFirstResponder -{ - ASDisplayNodeAssertMainThread(); - return [self __isFirstResponder]; -} - -- (BOOL)becomeFirstResponder -{ - ASDisplayNodeAssertMainThread(); - return [self __becomeFirstResponder]; -} - -- (BOOL)resignFirstResponder -{ - ASDisplayNodeAssertMainThread(); - return [self __resignFirstResponder]; -} - -- (BOOL)canPerformAction:(SEL)action withSender:(id)sender -{ - ASDisplayNodeAssertMainThread(); - return !self.layerBacked && [self.view canPerformAction:action withSender:sender]; -} - -- (CGFloat)alpha -{ - _bridge_prologue_read; - return _getFromViewOrLayer(opacity, alpha); -} - -- (void)setAlpha:(CGFloat)newAlpha -{ - _bridge_prologue_write; - _setToViewOrLayer(opacity, newAlpha, alpha, newAlpha); -} - -- (CGFloat)cornerRadius -{ - AS::MutexLocker l(__instanceLock__); - return _cornerRadius; -} - -- (void)setCornerRadius:(CGFloat)newCornerRadius -{ - [self updateCornerRoundingWithType:self.cornerRoundingType cornerRadius:newCornerRadius]; -} - -- (ASCornerRoundingType)cornerRoundingType -{ - AS::MutexLocker l(__instanceLock__); - return _cornerRoundingType; -} - -- (void)setCornerRoundingType:(ASCornerRoundingType)newRoundingType -{ - [self updateCornerRoundingWithType:newRoundingType cornerRadius:self.cornerRadius]; -} - -- (NSString *)contentsGravity -{ - _bridge_prologue_read; - return _getFromLayer(contentsGravity); -} - -- (void)setContentsGravity:(NSString *)newContentsGravity -{ - _bridge_prologue_write; - _setToLayer(contentsGravity, newContentsGravity); -} - -- (CGRect)contentsRect -{ - _bridge_prologue_read; - return _getFromLayer(contentsRect); -} - -- (void)setContentsRect:(CGRect)newContentsRect -{ - _bridge_prologue_write; - _setToLayer(contentsRect, newContentsRect); -} - -- (CGRect)contentsCenter -{ - _bridge_prologue_read; - return _getFromLayer(contentsCenter); -} - -- (void)setContentsCenter:(CGRect)newContentsCenter -{ - _bridge_prologue_write; - _setToLayer(contentsCenter, newContentsCenter); -} - -- (CGFloat)contentsScale -{ - _bridge_prologue_read; - return _getFromLayer(contentsScale); -} - -- (void)setContentsScale:(CGFloat)newContentsScale -{ - _bridge_prologue_write; - _setToLayer(contentsScale, newContentsScale); -} - -- (CGFloat)rasterizationScale -{ - _bridge_prologue_read; - return _getFromLayer(rasterizationScale); -} - -- (void)setRasterizationScale:(CGFloat)newRasterizationScale -{ - _bridge_prologue_write; - _setToLayer(rasterizationScale, newRasterizationScale); -} - -- (CGRect)bounds -{ - _bridge_prologue_read; - return _getFromViewOrLayer(bounds, bounds); -} - -- (void)setBounds:(CGRect)newBounds -{ - _bridge_prologue_write; - _setToViewOrLayer(bounds, newBounds, bounds, newBounds); - self.threadSafeBounds = newBounds; -} - -- (CGRect)frame -{ - _bridge_prologue_read; - - // Frame is only defined when transform is identity. -//#if DEBUG -// // Checking if the transform is identity is expensive, so disable when unnecessary. We have assertions on in Release, so DEBUG is the only way I know of. -// ASDisplayNodeAssert(CATransform3DIsIdentity(self.transform), @"-[ASDisplayNode frame] - self.transform must be identity in order to use the frame property. (From Apple's UIView documentation: If the transform property is not the identity transform, the value of this property is undefined and therefore should be ignored.)"); -//#endif - - CGPoint position = self.position; - CGRect bounds = self.bounds; - CGPoint anchorPoint = self.anchorPoint; - CGPoint origin = CGPointMake(position.x - bounds.size.width * anchorPoint.x, - position.y - bounds.size.height * anchorPoint.y); - return CGRectMake(origin.x, origin.y, bounds.size.width, bounds.size.height); -} - -- (void)setFrame:(CGRect)rect -{ - BOOL setToView = NO; - BOOL setToLayer = NO; - CGRect newBounds = CGRectZero; - CGPoint newPosition = CGPointZero; - BOOL nodeLoaded = NO; - BOOL isMainThread = ASDisplayNodeThreadIsMain(); - { - _bridge_prologue_write; - - // For classes like ASTableNode, ASCollectionNode, ASScrollNode and similar - make sure UIView gets setFrame: - struct ASDisplayNodeFlags flags = _flags; - BOOL specialPropertiesHandling = ASDisplayNodeNeedsSpecialPropertiesHandling(checkFlag(Synchronous), flags.layerBacked); - - nodeLoaded = _loaded(self); - if (!specialPropertiesHandling) { - BOOL canReadProperties = isMainThread || !nodeLoaded; - if (canReadProperties) { - // We don't have to set frame directly, and we can read current properties. - // Compute a new bounds and position and set them on self. - CALayer *layer = _layer; - CGPoint origin = (nodeLoaded ? layer.bounds.origin : self.bounds.origin); - CGPoint anchorPoint = (nodeLoaded ? layer.anchorPoint : self.anchorPoint); - - ASBoundsAndPositionForFrame(rect, origin, anchorPoint, &newBounds, &newPosition); - - if (ASIsCGRectValidForLayout(newBounds) == NO || ASIsCGPositionValidForLayout(newPosition) == NO) { - ASDisplayNodeAssertNonFatal(NO, @"-[ASDisplayNode setFrame:] - The new frame (%@) is invalid and unsafe to be set.", NSStringFromCGRect(rect)); - return; - } - - if (nodeLoaded) { - setToLayer = YES; - } else { - self.bounds = newBounds; - self.position = newPosition; - } - } else { - // We don't have to set frame directly, but we can't read properties. - // Store the frame in our pending state, and it'll get decomposed into - // bounds and position when the pending state is applied. - _ASPendingState *pendingState = ASDisplayNodeGetPendingState(self); - if (nodeLoaded && !pendingState.hasChanges) { - [[ASPendingStateController sharedInstance] registerNode:self]; - } - pendingState.frame = rect; - } - } else { - if (nodeLoaded && isMainThread) { - // We do have to set frame directly, and we're on main thread with a loaded node. - // Just set the frame on the view. - // NOTE: Frame is only defined when transform is identity because we explicitly diverge from CALayer behavior and define frame without transform. - setToView = YES; - } else { - // We do have to set frame directly, but either the node isn't loaded or we're on a non-main thread. - // Set the frame on the pending state, and it'll call setFrame: when applied. - _ASPendingState *pendingState = ASDisplayNodeGetPendingState(self); - if (nodeLoaded && !pendingState.hasChanges) { - [[ASPendingStateController sharedInstance] registerNode:self]; - } - pendingState.frame = rect; - } - } - } - - if (setToView) { - ASDisplayNodeAssertTrue(nodeLoaded && isMainThread); - _view.frame = rect; - } else if (setToLayer) { - ASDisplayNodeAssertTrue(nodeLoaded && isMainThread); - _layer.bounds = newBounds; - _layer.position = newPosition; - } -} - -- (void)setNeedsDisplay -{ - BOOL isRasterized = NO; - BOOL shouldApply = NO; - id viewOrLayer = nil; - { - _bridge_prologue_write; - isRasterized = _hierarchyState & ASHierarchyStateRasterized; - shouldApply = ASDisplayNodeShouldApplyBridgedWriteToView(self); - viewOrLayer = _view ?: _layer; - - if (isRasterized == NO && shouldApply == NO) { - // We can't release the lock before applying to pending state, or it may be flushed before it can be applied. - [ASDisplayNodeGetPendingState(self) setNeedsDisplay]; - } - } - - if (isRasterized) { - ASPerformBlockOnMainThread(^{ - // The below operation must be performed on the main thread to ensure against an extremely rare deadlock, where a parent node - // begins materializing the view / layer hierarchy (locking itself or a descendant) while this node walks up - // the tree and requires locking that node to access .rasterizesSubtree. - // For this reason, this method should be avoided when possible. Use _hierarchyState & ASHierarchyStateRasterized. - ASDisplayNodeAssertMainThread(); - ASDisplayNode *rasterizedContainerNode = self.supernode; - while (rasterizedContainerNode) { - if (rasterizedContainerNode.rasterizesSubtree) { - break; - } - rasterizedContainerNode = rasterizedContainerNode.supernode; - } - [rasterizedContainerNode setNeedsDisplay]; - }); - } else { - if (shouldApply) { - // If not rasterized, and the node is loaded (meaning we certainly have a view or layer), send a - // message to the view/layer first. This is because __setNeedsDisplay calls as scheduleNodeForDisplay, - // which may call -displayIfNeeded. We want to ensure the needsDisplay flag is set now, and then cleared. - [viewOrLayer setNeedsDisplay]; - } - [self __setNeedsDisplay]; - } -} - -- (void)setNeedsLayout -{ - BOOL shouldApply = NO; - BOOL loaded = NO; - id viewOrLayer = nil; - { - _bridge_prologue_write; - shouldApply = ASDisplayNodeShouldApplyBridgedWriteToView(self); - loaded = _loaded(self); - viewOrLayer = _view ?: _layer; - if (shouldApply == NO && loaded) { - // The node is loaded but we're not on main. - // We will call [self __setNeedsLayout] when we apply the pending state. - // We need to call it on main if the node is loaded to support automatic subnode management. - // We can't release the lock before applying to pending state, or it may be flushed before it can be applied. - [ASDisplayNodeGetPendingState(self) setNeedsLayout]; - } - } - - if (shouldApply) { - // The node is loaded and we're on main. - // Quite the opposite of setNeedsDisplay, we must call __setNeedsLayout before messaging - // the view or layer to ensure that measurement and implicitly added subnodes have been handled. - [self __setNeedsLayout]; - [viewOrLayer setNeedsLayout]; - } else if (loaded == NO) { - // The node is not loaded and we're not on main. - [self __setNeedsLayout]; - } -} - -- (void)layoutIfNeeded -{ - BOOL shouldApply = NO; - BOOL loaded = NO; - id viewOrLayer = nil; - { - _bridge_prologue_write; - shouldApply = ASDisplayNodeShouldApplyBridgedWriteToView(self); - loaded = _loaded(self); - viewOrLayer = _view ?: _layer; - if (shouldApply == NO && loaded) { - // The node is loaded but we're not on main. - // We will call layoutIfNeeded on the view or layer when we apply the pending state. __layout will in turn be called on us (see -[_ASDisplayLayer layoutSublayers]). - // We need to call it on main if the node is loaded to support automatic subnode management. - // We can't release the lock before applying to pending state, or it may be flushed before it can be applied. - [ASDisplayNodeGetPendingState(self) layoutIfNeeded]; - } - } - - if (shouldApply) { - // The node is loaded and we're on main. - // Message the view or layer which in turn will call __layout on us (see -[_ASDisplayLayer layoutSublayers]). - [viewOrLayer layoutIfNeeded]; - } else if (loaded == NO) { - // The node is not loaded and we're not on main. - [self __layout]; - } -} - -- (BOOL)isOpaque -{ - _bridge_prologue_read; - return _getFromLayer(opaque); -} - -- (void)setOpaque:(BOOL)newOpaque -{ - _bridge_prologue_write; - - BOOL shouldApply = ASDisplayNodeShouldApplyBridgedWriteToView(self); - - if (shouldApply) { - BOOL oldOpaque = _layer.opaque; - _layer.opaque = newOpaque; - if (oldOpaque != newOpaque) { - [self setNeedsDisplay]; - } - } else { - // NOTE: If we're in the background, we cannot read the current value of self.opaque (if loaded). - // When the pending state is applied to the view on main, we will call `setNeedsDisplay` if - // the new opaque value doesn't match the one on the layer. - ASDisplayNodeGetPendingState(self).opaque = newOpaque; - } -} - -- (BOOL)isUserInteractionEnabled -{ - _bridge_prologue_read; - if (_flags.layerBacked) return NO; - return _getFromViewOnly(userInteractionEnabled); -} - -- (void)setUserInteractionEnabled:(BOOL)enabled -{ - _bridge_prologue_write; - _setToViewOnly(userInteractionEnabled, enabled); -} -#if TARGET_OS_IOS -- (BOOL)isExclusiveTouch -{ - _bridge_prologue_read; - return _getFromViewOnly(exclusiveTouch); -} - -- (void)setExclusiveTouch:(BOOL)exclusiveTouch -{ - _bridge_prologue_write; - _setToViewOnly(exclusiveTouch, exclusiveTouch); -} -#endif -- (BOOL)clipsToBounds -{ - _bridge_prologue_read; - return _getFromViewOrLayer(masksToBounds, clipsToBounds); -} - -- (void)setClipsToBounds:(BOOL)clips -{ - _bridge_prologue_write; - _setToViewOrLayer(masksToBounds, clips, clipsToBounds, clips); -} - -- (CGPoint)anchorPoint -{ - _bridge_prologue_read; - return _getFromLayer(anchorPoint); -} - -- (void)setAnchorPoint:(CGPoint)newAnchorPoint -{ - _bridge_prologue_write; - _setToLayer(anchorPoint, newAnchorPoint); -} - -- (CGPoint)position -{ - _bridge_prologue_read; - return _getFromLayer(position); -} - -- (void)setPosition:(CGPoint)newPosition -{ - _bridge_prologue_write; - _setToLayer(position, newPosition); -} - -- (CGFloat)zPosition -{ - _bridge_prologue_read; - return _getFromLayer(zPosition); -} - -- (void)setZPosition:(CGFloat)newPosition -{ - _bridge_prologue_write; - _setToLayer(zPosition, newPosition); -} - -- (CATransform3D)transform -{ - _bridge_prologue_read; - return _getFromLayer(transform); -} - -- (void)setTransform:(CATransform3D)newTransform -{ - _bridge_prologue_write; - _setToLayer(transform, newTransform); -} - -- (CATransform3D)subnodeTransform -{ - _bridge_prologue_read; - return _getFromLayer(sublayerTransform); -} - -- (void)setSubnodeTransform:(CATransform3D)newSubnodeTransform -{ - _bridge_prologue_write; - _setToLayer(sublayerTransform, newSubnodeTransform); -} - -- (id)contents -{ - _bridge_prologue_read; - return _getFromLayer(contents); -} - -- (void)setContents:(id)newContents -{ - _bridge_prologue_write; - _setToLayer(contents, newContents); -} - -- (BOOL)isHidden -{ - _bridge_prologue_read; - return _getFromViewOrLayer(hidden, hidden); -} - -- (void)setHidden:(BOOL)flag -{ - _bridge_prologue_write; - _setToViewOrLayer(hidden, flag, hidden, flag); -} - -- (BOOL)needsDisplayOnBoundsChange -{ - _bridge_prologue_read; - return _getFromLayer(needsDisplayOnBoundsChange); -} - -- (void)setNeedsDisplayOnBoundsChange:(BOOL)flag -{ - _bridge_prologue_write; - _setToLayer(needsDisplayOnBoundsChange, flag); -} - -- (BOOL)autoresizesSubviews -{ - _bridge_prologue_read; - ASDisplayNodeAssert(!_flags.layerBacked, @"Danger: this property is undefined on layer-backed nodes."); - return _getFromViewOnly(autoresizesSubviews); -} - -- (void)setAutoresizesSubviews:(BOOL)flag -{ - _bridge_prologue_write; - ASDisplayNodeAssert(!_flags.layerBacked, @"Danger: this property is undefined on layer-backed nodes."); - _setToViewOnly(autoresizesSubviews, flag); -} - -- (UIViewAutoresizing)autoresizingMask -{ - _bridge_prologue_read; - ASDisplayNodeAssert(!_flags.layerBacked, @"Danger: this property is undefined on layer-backed nodes."); - return _getFromViewOnly(autoresizingMask); -} - -- (void)setAutoresizingMask:(UIViewAutoresizing)mask -{ - _bridge_prologue_write; - ASDisplayNodeAssert(!_flags.layerBacked, @"Danger: this property is undefined on layer-backed nodes."); - _setToViewOnly(autoresizingMask, mask); -} - -- (UIViewContentMode)contentMode -{ - _bridge_prologue_read; - if (_loaded(self)) { - if (_flags.layerBacked) { - return ASDisplayNodeUIContentModeFromCAContentsGravity(_layer.contentsGravity); - } else { - return _view.contentMode; - } - } else { - return ASDisplayNodeGetPendingState(self).contentMode; - } -} - -- (void)setContentMode:(UIViewContentMode)contentMode -{ - _bridge_prologue_write; - BOOL shouldApply = ASDisplayNodeShouldApplyBridgedWriteToView(self); - if (shouldApply) { - if (_flags.layerBacked) { - _layer.contentsGravity = ASDisplayNodeCAContentsGravityFromUIContentMode(contentMode); - } else { - _view.contentMode = contentMode; - } - } else { - ASDisplayNodeGetPendingState(self).contentMode = contentMode; - } -} - -- (void)setAccessibilityCustomActions:(NSArray *)accessibilityCustomActions -{ - _bridge_prologue_write; - BOOL shouldApply = ASDisplayNodeShouldApplyBridgedWriteToView(self); - if (shouldApply) { - if (_flags.layerBacked) { - } else { - _view.accessibilityCustomActions = accessibilityCustomActions; - } - } else { - ASDisplayNodeGetPendingState(self).accessibilityCustomActions = accessibilityCustomActions; - } -} - -- (UIColor *)backgroundColor -{ - _bridge_prologue_read; - return [UIColor colorWithCGColor:_getFromLayer(backgroundColor)]; -} - -- (void)setBackgroundColor:(UIColor *)newBackgroundColor -{ - _bridge_prologue_write; - - CGColorRef newBackgroundCGColor = CGColorRetain([newBackgroundColor CGColor]); - BOOL shouldApply = ASDisplayNodeShouldApplyBridgedWriteToView(self); - - if (shouldApply) { - CGColorRef oldBackgroundCGColor = CGColorRetain(_layer.backgroundColor); - - BOOL specialPropertiesHandling = ASDisplayNodeNeedsSpecialPropertiesHandling(checkFlag(Synchronous), _flags.layerBacked); - if (specialPropertiesHandling) { - _view.backgroundColor = newBackgroundColor; - } else { - _layer.backgroundColor = newBackgroundCGColor; - } - - if (!CGColorEqualToColor(oldBackgroundCGColor, newBackgroundCGColor)) { - [self setNeedsDisplay]; - } - - CGColorRelease(oldBackgroundCGColor); - } else { - // NOTE: If we're in the background, we cannot read the current value of bgcolor (if loaded). - // When the pending state is applied to the view on main, we will call `setNeedsDisplay` if - // the new background color doesn't match the one on the layer. - ASDisplayNodeGetPendingState(self).backgroundColor = newBackgroundCGColor; - } - CGColorRelease(newBackgroundCGColor); -} - -- (UIColor *)tintColor -{ - _bridge_prologue_read; - ASDisplayNodeAssert(!_flags.layerBacked, @"Danger: this property is undefined on layer-backed nodes."); - return _getFromViewOnly(tintColor); -} - -- (void)setTintColor:(UIColor *)color -{ - _bridge_prologue_write; - ASDisplayNodeAssert(!_flags.layerBacked, @"Danger: this property is undefined on layer-backed nodes."); - _setToViewOnly(tintColor, color); -} - -- (void)tintColorDidChange -{ - // ignore this, allow subclasses to be notified -} - -- (CGColorRef)shadowColor -{ - _bridge_prologue_read; - return _getFromLayer(shadowColor); -} - -- (void)setShadowColor:(CGColorRef)colorValue -{ - _bridge_prologue_write; - _setToLayer(shadowColor, colorValue); -} - -- (CGFloat)shadowOpacity -{ - _bridge_prologue_read; - return _getFromLayer(shadowOpacity); -} - -- (void)setShadowOpacity:(CGFloat)opacity -{ - _bridge_prologue_write; - _setToLayer(shadowOpacity, opacity); -} - -- (CGSize)shadowOffset -{ - _bridge_prologue_read; - return _getFromLayer(shadowOffset); -} - -- (void)setShadowOffset:(CGSize)offset -{ - _bridge_prologue_write; - _setToLayer(shadowOffset, offset); -} - -- (CGFloat)shadowRadius -{ - _bridge_prologue_read; - return _getFromLayer(shadowRadius); -} - -- (void)setShadowRadius:(CGFloat)radius -{ - _bridge_prologue_write; - _setToLayer(shadowRadius, radius); -} - -- (CGFloat)borderWidth -{ - _bridge_prologue_read; - return _getFromLayer(borderWidth); -} - -- (void)setBorderWidth:(CGFloat)width -{ - _bridge_prologue_write; - _setToLayer(borderWidth, width); -} - -- (CGColorRef)borderColor -{ - _bridge_prologue_read; - return _getFromLayer(borderColor); -} - -- (void)setBorderColor:(CGColorRef)colorValue -{ - _bridge_prologue_write; - _setToLayer(borderColor, colorValue); -} - -- (BOOL)allowsGroupOpacity -{ - _bridge_prologue_read; - return _getFromLayer(allowsGroupOpacity); -} - -- (void)setAllowsGroupOpacity:(BOOL)allowsGroupOpacity -{ - _bridge_prologue_write; - _setToLayer(allowsGroupOpacity, allowsGroupOpacity); -} - -- (BOOL)allowsEdgeAntialiasing -{ - _bridge_prologue_read; - return _getFromLayer(allowsEdgeAntialiasing); -} - -- (void)setAllowsEdgeAntialiasing:(BOOL)allowsEdgeAntialiasing -{ - _bridge_prologue_write; - _setToLayer(allowsEdgeAntialiasing, allowsEdgeAntialiasing); -} - -- (unsigned int)edgeAntialiasingMask -{ - _bridge_prologue_read; - return _getFromLayer(edgeAntialiasingMask); -} - -- (void)setEdgeAntialiasingMask:(unsigned int)edgeAntialiasingMask -{ - _bridge_prologue_write; - _setToLayer(edgeAntialiasingMask, edgeAntialiasingMask); -} - -- (UISemanticContentAttribute)semanticContentAttribute -{ - _bridge_prologue_read; - return _getFromViewOnly(semanticContentAttribute); -} - -- (void)setSemanticContentAttribute:(UISemanticContentAttribute)semanticContentAttribute -{ - _bridge_prologue_write; - _setToViewOnly(semanticContentAttribute, semanticContentAttribute); -#if YOGA - [self semanticContentAttributeDidChange:semanticContentAttribute]; -#endif -} - -- (UIEdgeInsets)layoutMargins -{ - _bridge_prologue_read; - ASDisplayNodeAssert(!_flags.layerBacked, @"Danger: this property is undefined on layer-backed nodes."); - UIEdgeInsets margins = _getFromViewOnly(layoutMargins); - - if (!AS_AT_LEAST_IOS11 && self.insetsLayoutMarginsFromSafeArea) { - UIEdgeInsets safeArea = self.safeAreaInsets; - margins = ASConcatInsets(margins, safeArea); - } - - return margins; -} - -- (void)setLayoutMargins:(UIEdgeInsets)layoutMargins -{ - _bridge_prologue_write; - ASDisplayNodeAssert(!_flags.layerBacked, @"Danger: this property is undefined on layer-backed nodes."); - _setToViewOnly(layoutMargins, layoutMargins); -} - -- (BOOL)preservesSuperviewLayoutMargins -{ - _bridge_prologue_read; - ASDisplayNodeAssert(!_flags.layerBacked, @"Danger: this property is undefined on layer-backed nodes."); - return _getFromViewOnly(preservesSuperviewLayoutMargins); -} - -- (void)setPreservesSuperviewLayoutMargins:(BOOL)preservesSuperviewLayoutMargins -{ - _bridge_prologue_write; - ASDisplayNodeAssert(!_flags.layerBacked, @"Danger: this property is undefined on layer-backed nodes."); - _setToViewOnly(preservesSuperviewLayoutMargins, preservesSuperviewLayoutMargins); -} - -- (void)layoutMarginsDidChange -{ - ASDisplayNodeAssertMainThread(); - - if (self.automaticallyRelayoutOnLayoutMarginsChanges) { - [self setNeedsLayout]; - } -} - -- (UIEdgeInsets)safeAreaInsets -{ - _bridge_prologue_read; - - if (AS_AVAILABLE_IOS(11.0)) { - if (!_flags.layerBacked && _loaded(self)) { - return self.view.safeAreaInsets; - } - } - return _fallbackSafeAreaInsets; -} - -- (BOOL)insetsLayoutMarginsFromSafeArea -{ - _bridge_prologue_read; - - return [self _locked_insetsLayoutMarginsFromSafeArea]; -} - -- (void)setInsetsLayoutMarginsFromSafeArea:(BOOL)insetsLayoutMarginsFromSafeArea -{ - ASDisplayNodeAssertThreadAffinity(self); - BOOL shouldNotifyAboutUpdate; - { - _bridge_prologue_write; - - _fallbackInsetsLayoutMarginsFromSafeArea = insetsLayoutMarginsFromSafeArea; - - if (AS_AVAILABLE_IOS(11.0)) { - if (!_flags.layerBacked) { - _setToViewOnly(insetsLayoutMarginsFromSafeArea, insetsLayoutMarginsFromSafeArea); - } - } - - shouldNotifyAboutUpdate = _loaded(self) && (!AS_AT_LEAST_IOS11 || _flags.layerBacked); - } - - if (shouldNotifyAboutUpdate) { - [self layoutMarginsDidChange]; - } -} - -- (void)safeAreaInsetsDidChange -{ - ASDisplayNodeAssertMainThread(); - - if (self.automaticallyRelayoutOnSafeAreaChanges) { - [self setNeedsLayout]; - } - - [self _fallbackUpdateSafeAreaOnChildren]; -} - -@end - -@implementation ASDisplayNode (InternalPropertyBridge) - -- (CGFloat)layerCornerRadius -{ - _bridge_prologue_read; - return _getFromLayer(cornerRadius); -} - -- (void)setLayerCornerRadius:(CGFloat)newLayerCornerRadius -{ - _bridge_prologue_write; - _setToLayer(cornerRadius, newLayerCornerRadius); -} - -- (BOOL)_locked_insetsLayoutMarginsFromSafeArea -{ - ASAssertLocked(__instanceLock__); - if (AS_AVAILABLE_IOS(11.0)) { - if (!_flags.layerBacked) { - return _getFromViewOnly(insetsLayoutMarginsFromSafeArea); - } - } - return _fallbackInsetsLayoutMarginsFromSafeArea; -} - -@end - -#pragma mark - UIViewBridgeAccessibility - -// ASDK supports accessibility for view or layer backed nodes. To be able to provide support for layer backed -// nodes, properties for all of the UIAccessibility protocol defined properties need to be provided an held in sync -// between node and view - -// Helper function with following logic: -// - If the node is not loaded yet use the property from the pending state -// - In case the node is loaded -// - Check if the node has a view and get the value from the view if loaded or from the pending state -// - If view is not available, e.g. the node is layer backed return the property value -#define _getAccessibilityFromViewOrProperty(nodeProperty, viewAndPendingViewStateProperty) _loaded(self) ? \ -(_view ? _view.viewAndPendingViewStateProperty : nodeProperty )\ -: ASDisplayNodeGetPendingState(self).viewAndPendingViewStateProperty - -// Helper function to set property values on pending state or view and property if loaded -#define _setAccessibilityToViewAndProperty(nodeProperty, nodeValueExpr, viewAndPendingViewStateProperty, viewAndPendingViewStateExpr) \ -nodeProperty = nodeValueExpr; _setToViewOnly(viewAndPendingViewStateProperty, viewAndPendingViewStateExpr) - -@implementation ASDisplayNode (UIViewBridgeAccessibility) - -// iOS 11 only properties. Add this to silence "unimplemented selector" warnings -// in old SDKs. If the caller doesn't respect our API_AVAILABLE attributes, then they -// get an appropriate "unrecognized selector" runtime error. -#if __IPHONE_OS_VERSION_MAX_ALLOWED < __IPHONE_11_0 -@dynamic accessibilityAttributedLabel, accessibilityAttributedHint, accessibilityAttributedValue; -#endif - -- (BOOL)isAccessibilityElement -{ - _bridge_prologue_read; - return _getAccessibilityFromViewOrProperty(_isAccessibilityElement, isAccessibilityElement); -} - -- (void)setIsAccessibilityElement:(BOOL)isAccessibilityElement -{ - _bridge_prologue_write; - _setAccessibilityToViewAndProperty(_isAccessibilityElement, isAccessibilityElement, isAccessibilityElement, isAccessibilityElement); -} - -- (NSString *)accessibilityLabel -{ - _bridge_prologue_read; - return _getAccessibilityFromViewOrProperty(_accessibilityLabel, accessibilityLabel); -} - -- (void)setAccessibilityLabel:(NSString *)accessibilityLabel -{ - _bridge_prologue_write; - _setAccessibilityToViewAndProperty(_accessibilityLabel, accessibilityLabel, accessibilityLabel, accessibilityLabel); -#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_11_0 - if (AS_AVAILABLE_IOS_TVOS(11, 11)) { - NSAttributedString *accessibilityAttributedLabel = accessibilityLabel ? [[NSAttributedString alloc] initWithString:accessibilityLabel] : nil; - _setAccessibilityToViewAndProperty(_accessibilityAttributedLabel, accessibilityAttributedLabel, accessibilityAttributedLabel, accessibilityAttributedLabel); - } -#endif -} - -#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_11_0 -- (NSAttributedString *)accessibilityAttributedLabel -{ - _bridge_prologue_read; - return _getAccessibilityFromViewOrProperty(_accessibilityAttributedLabel, accessibilityAttributedLabel); -} - -- (void)setAccessibilityAttributedLabel:(NSAttributedString *)accessibilityAttributedLabel -{ - _bridge_prologue_write; - { _setAccessibilityToViewAndProperty(_accessibilityAttributedLabel, accessibilityAttributedLabel, accessibilityAttributedLabel, accessibilityAttributedLabel); } - { _setAccessibilityToViewAndProperty(_accessibilityLabel, accessibilityAttributedLabel.string, accessibilityLabel, accessibilityAttributedLabel.string); } -} -#endif - -- (NSString *)accessibilityHint -{ - _bridge_prologue_read; - return _getAccessibilityFromViewOrProperty(_accessibilityHint, accessibilityHint); -} - -- (void)setAccessibilityHint:(NSString *)accessibilityHint -{ - _bridge_prologue_write; - _setAccessibilityToViewAndProperty(_accessibilityHint, accessibilityHint, accessibilityHint, accessibilityHint); -#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_11_0 - if (AS_AVAILABLE_IOS_TVOS(11, 11)) { - NSAttributedString *accessibilityAttributedHint = accessibilityHint ? [[NSAttributedString alloc] initWithString:accessibilityHint] : nil; - _setAccessibilityToViewAndProperty(_accessibilityAttributedHint, accessibilityAttributedHint, accessibilityAttributedHint, accessibilityAttributedHint); - } -#endif -} - -#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_11_0 -- (NSAttributedString *)accessibilityAttributedHint -{ - _bridge_prologue_read; - return _getAccessibilityFromViewOrProperty(_accessibilityAttributedHint, accessibilityAttributedHint); -} - -- (void)setAccessibilityAttributedHint:(NSAttributedString *)accessibilityAttributedHint -{ - _bridge_prologue_write; - { _setAccessibilityToViewAndProperty(_accessibilityAttributedHint, accessibilityAttributedHint, accessibilityAttributedHint, accessibilityAttributedHint); } - - { _setAccessibilityToViewAndProperty(_accessibilityHint, accessibilityAttributedHint.string, accessibilityHint, accessibilityAttributedHint.string); } -} -#endif - -- (NSString *)accessibilityValue -{ - _bridge_prologue_read; - return _getAccessibilityFromViewOrProperty(_accessibilityValue, accessibilityValue); -} - -- (void)setAccessibilityValue:(NSString *)accessibilityValue -{ - _bridge_prologue_write; - _setAccessibilityToViewAndProperty(_accessibilityValue, accessibilityValue, accessibilityValue, accessibilityValue); -#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_11_0 - if (AS_AVAILABLE_IOS_TVOS(11, 11)) { - NSAttributedString *accessibilityAttributedValue = accessibilityValue ? [[NSAttributedString alloc] initWithString:accessibilityValue] : nil; - _setAccessibilityToViewAndProperty(_accessibilityAttributedValue, accessibilityAttributedValue, accessibilityAttributedValue, accessibilityAttributedValue); - } -#endif -} - -#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_11_0 -- (NSAttributedString *)accessibilityAttributedValue -{ - _bridge_prologue_read; - return _getAccessibilityFromViewOrProperty(_accessibilityAttributedValue, accessibilityAttributedValue); -} - -- (void)setAccessibilityAttributedValue:(NSAttributedString *)accessibilityAttributedValue -{ - _bridge_prologue_write; - { _setAccessibilityToViewAndProperty(_accessibilityAttributedValue, accessibilityAttributedValue, accessibilityAttributedValue, accessibilityAttributedValue); } - { _setAccessibilityToViewAndProperty(_accessibilityValue, accessibilityAttributedValue.string, accessibilityValue, accessibilityAttributedValue.string); } -} -#endif - -- (UIAccessibilityTraits)accessibilityTraits -{ - _bridge_prologue_read; - return _getAccessibilityFromViewOrProperty(_accessibilityTraits, accessibilityTraits); -} - -- (void)setAccessibilityTraits:(UIAccessibilityTraits)accessibilityTraits -{ - _bridge_prologue_write; - _setAccessibilityToViewAndProperty(_accessibilityTraits, accessibilityTraits, accessibilityTraits, accessibilityTraits); -} - -- (CGRect)accessibilityFrame -{ - _bridge_prologue_read; - return _getAccessibilityFromViewOrProperty(_accessibilityFrame, accessibilityFrame); -} - -- (void)setAccessibilityFrame:(CGRect)accessibilityFrame -{ - _bridge_prologue_write; - _setAccessibilityToViewAndProperty(_accessibilityFrame, accessibilityFrame, accessibilityFrame, accessibilityFrame); -} - -- (NSString *)accessibilityLanguage -{ - _bridge_prologue_read; - return _getAccessibilityFromViewOrProperty(_accessibilityLanguage, accessibilityLanguage); -} - -- (void)setAccessibilityLanguage:(NSString *)accessibilityLanguage -{ - _bridge_prologue_write; - _setAccessibilityToViewAndProperty(_accessibilityLanguage, accessibilityLanguage, accessibilityLanguage, accessibilityLanguage); -} - -- (BOOL)accessibilityElementsHidden -{ - _bridge_prologue_read; - return _getAccessibilityFromViewOrProperty(_accessibilityElementsHidden, accessibilityElementsHidden); -} - -- (void)setAccessibilityElementsHidden:(BOOL)accessibilityElementsHidden -{ - _bridge_prologue_write; - _setAccessibilityToViewAndProperty(_accessibilityElementsHidden, accessibilityElementsHidden, accessibilityElementsHidden, accessibilityElementsHidden); -} - -- (BOOL)accessibilityViewIsModal -{ - _bridge_prologue_read; - return _getAccessibilityFromViewOrProperty(_accessibilityViewIsModal, accessibilityViewIsModal); -} - -- (void)setAccessibilityViewIsModal:(BOOL)accessibilityViewIsModal -{ - _bridge_prologue_write; - _setAccessibilityToViewAndProperty(_accessibilityViewIsModal, accessibilityViewIsModal, accessibilityViewIsModal, accessibilityViewIsModal); -} - -- (BOOL)shouldGroupAccessibilityChildren -{ - _bridge_prologue_read; - return _getAccessibilityFromViewOrProperty(_shouldGroupAccessibilityChildren, shouldGroupAccessibilityChildren); -} - -- (void)setShouldGroupAccessibilityChildren:(BOOL)shouldGroupAccessibilityChildren -{ - _bridge_prologue_write; - _setAccessibilityToViewAndProperty(_shouldGroupAccessibilityChildren, shouldGroupAccessibilityChildren, shouldGroupAccessibilityChildren, shouldGroupAccessibilityChildren); -} - -- (NSString *)accessibilityIdentifier -{ - _bridge_prologue_read; - return _getAccessibilityFromViewOrProperty(_accessibilityIdentifier, accessibilityIdentifier); -} - -- (void)setAccessibilityIdentifier:(NSString *)accessibilityIdentifier -{ - _bridge_prologue_write; - _setAccessibilityToViewAndProperty(_accessibilityIdentifier, accessibilityIdentifier, accessibilityIdentifier, accessibilityIdentifier); -} - -- (void)setAccessibilityNavigationStyle:(UIAccessibilityNavigationStyle)accessibilityNavigationStyle -{ - _bridge_prologue_write; - _setAccessibilityToViewAndProperty(_accessibilityNavigationStyle, accessibilityNavigationStyle, accessibilityNavigationStyle, accessibilityNavigationStyle); -} - -- (UIAccessibilityNavigationStyle)accessibilityNavigationStyle -{ - _bridge_prologue_read; - return _getAccessibilityFromViewOrProperty(_accessibilityNavigationStyle, accessibilityNavigationStyle); -} - -#if TARGET_OS_TV -- (void)setAccessibilityHeaderElements:(NSArray *)accessibilityHeaderElements -{ - _bridge_prologue_write; - _setAccessibilityToViewAndProperty(_accessibilityHeaderElements, accessibilityHeaderElements, accessibilityHeaderElements, accessibilityHeaderElements); -} - -- (NSArray *)accessibilityHeaderElements -{ - _bridge_prologue_read; - return _getAccessibilityFromViewOrProperty(_accessibilityHeaderElements, accessibilityHeaderElements); -} -#endif - -- (void)setAccessibilityActivationPoint:(CGPoint)accessibilityActivationPoint -{ - _bridge_prologue_write; - _setAccessibilityToViewAndProperty(_accessibilityActivationPoint, accessibilityActivationPoint, accessibilityActivationPoint, accessibilityActivationPoint); -} - -- (CGPoint)accessibilityActivationPoint -{ - _bridge_prologue_read; - return _getAccessibilityFromViewOrProperty(_accessibilityActivationPoint, accessibilityActivationPoint); -} - -- (void)setAccessibilityPath:(UIBezierPath *)accessibilityPath -{ - _bridge_prologue_write; - _setAccessibilityToViewAndProperty(_accessibilityPath, accessibilityPath, accessibilityPath, accessibilityPath); -} - -- (UIBezierPath *)accessibilityPath -{ - _bridge_prologue_read; - return _getAccessibilityFromViewOrProperty(_accessibilityPath, accessibilityPath); -} - -- (NSInteger)accessibilityElementCount -{ - _bridge_prologue_read; - return _getFromViewOnly(accessibilityElementCount); -} - -@end - - -#pragma mark - ASAsyncTransactionContainer - -@implementation ASDisplayNode (ASAsyncTransactionContainer) - -- (BOOL)asyncdisplaykit_isAsyncTransactionContainer -{ - _bridge_prologue_read; - return _getFromViewOrLayer(asyncdisplaykit_isAsyncTransactionContainer, asyncdisplaykit_isAsyncTransactionContainer); -} - -- (void)asyncdisplaykit_setAsyncTransactionContainer:(BOOL)asyncTransactionContainer -{ - _bridge_prologue_write; - _setToViewOrLayer(asyncdisplaykit_asyncTransactionContainer, asyncTransactionContainer, asyncdisplaykit_asyncTransactionContainer, asyncTransactionContainer); -} - -- (ASAsyncTransactionContainerState)asyncdisplaykit_asyncTransactionContainerState -{ - ASDisplayNodeAssertMainThread(); - return [_layer asyncdisplaykit_asyncTransactionContainerState]; -} - -- (void)asyncdisplaykit_cancelAsyncTransactions -{ - ASDisplayNodeAssertMainThread(); - [_layer asyncdisplaykit_cancelAsyncTransactions]; -} - -- (void)asyncdisplaykit_setCurrentAsyncTransaction:(_ASAsyncTransaction *)transaction -{ - _layer.asyncdisplaykit_currentAsyncTransaction = transaction; -} - -- (_ASAsyncTransaction *)asyncdisplaykit_currentAsyncTransaction -{ - return _layer.asyncdisplaykit_currentAsyncTransaction; -} - -@end diff --git a/submodules/AsyncDisplayKit/Source/ASDisplayNode.mm b/submodules/AsyncDisplayKit/Source/ASDisplayNode.mm deleted file mode 100644 index e8607e273c..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASDisplayNode.mm +++ /dev/null @@ -1,3803 +0,0 @@ -// -// ASDisplayNode.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import "ASDisplayNodeInternal.h" - -#import -#import -#import -#import "ASLayoutSpec+Subclasses.h" - -#import -#include - -#import -#import "_ASAsyncTransactionContainer+Private.h" -#import -#import -#import -#import "_ASPendingState.h" -#import "_ASScopeTimer.h" -#import -#import -#import -#import -#import -#import -#import -#import -#import "ASLayoutElementStylePrivate.h" -#import -#import "ASLayoutSpecPrivate.h" -#import -#import -#import "ASSignpost.h" -#import -#import "ASWeakProxy.h" -#import "ASResponderChainEnumerator.h" - -// Conditionally time these scopes to our debug ivars (only exist in debug/profile builds) -#if TIME_DISPLAYNODE_OPS - #define TIME_SCOPED(outVar) AS::ScopeTimer t(outVar) -#else - #define TIME_SCOPED(outVar) -#endif -// This is trying to merge non-rangeManaged with rangeManaged, so both range-managed and standalone nodes wait before firing their exit-visibility handlers, as UIViewController transitions now do rehosting at both start & end of animation. -// Enable this will mitigate interface updating state when coalescing disabled. -// TODO(wsdwsd0829): Rework enabling code to ensure that interface state behavior is not altered when ASCATransactionQueue is disabled. -#define ENABLE_NEW_EXIT_HIERARCHY_BEHAVIOR 0 - -using AS::MutexLocker; - -static ASDisplayNodeNonFatalErrorBlock _nonFatalErrorBlock = nil; - -// Forward declare CALayerDelegate protocol as the iOS 10 SDK moves CALayerDelegate from an informal delegate to a protocol. -// We have to forward declare the protocol as this place otherwise it will not compile compiling with an Base SDK < iOS 10 -@protocol CALayerDelegate; - -@interface ASDisplayNode () -/** - * See ASDisplayNodeInternal.h for ivars - */ - -@end - -@implementation ASDisplayNode - -@dynamic layoutElementType; - -@synthesize threadSafeBounds = _threadSafeBounds; - -static std::atomic_bool suppressesInvalidCollectionUpdateExceptions = ATOMIC_VAR_INIT(NO); -static std::atomic_bool storesUnflattenedLayouts = ATOMIC_VAR_INIT(NO); - -BOOL ASDisplayNodeSubclassOverridesSelector(Class subclass, SEL selector) -{ - return ASSubclassOverridesSelector([ASDisplayNode class], subclass, selector); -} - -// For classes like ASTableNode, ASCollectionNode, ASScrollNode and similar - we have to be sure to set certain properties -// like setFrame: and setBackgroundColor: directly to the UIView and not apply it to the layer only. -BOOL ASDisplayNodeNeedsSpecialPropertiesHandling(BOOL isSynchronous, BOOL isLayerBacked) -{ - return isSynchronous && !isLayerBacked; -} - -_ASPendingState *ASDisplayNodeGetPendingState(ASDisplayNode *node) -{ - ASLockScope(node); - _ASPendingState *result = node->_pendingViewState; - if (result == nil) { - result = [[_ASPendingState alloc] init]; - node->_pendingViewState = result; - } - return result; -} - -void StubImplementationWithNoArgs(id receiver) {} -void StubImplementationWithSizeRange(id receiver, ASSizeRange sr) {} -void StubImplementationWithTwoInterfaceStates(id receiver, ASInterfaceState s0, ASInterfaceState s1) {} - -/** - * Returns ASDisplayNodeFlags for the given class/instance. instance MAY BE NIL. - * - * @param c the class, required - * @param instance the instance, which may be nil. (If so, the class is inspected instead) - * @remarks The instance value is used only if we suspect the class may be dynamic (because it overloads - * +respondsToSelector: or -respondsToSelector.) In that case we use our "slow path", calling this - * method on each -init and passing the instance value. While this may seem like an unlikely scenario, - * it turns our our own internal tests use a dynamic class, so it's worth capturing this edge case. - * - * @return ASDisplayNode flags. - */ -static struct ASDisplayNodeFlags GetASDisplayNodeFlags(Class c, ASDisplayNode *instance) -{ - ASDisplayNodeCAssertNotNil(c, @"class is required"); - - struct ASDisplayNodeFlags flags = {0}; - - flags.isInHierarchy = NO; - flags.displaysAsynchronously = YES; - flags.shouldAnimateSizeChanges = YES; - flags.implementsDrawRect = ([c respondsToSelector:@selector(drawRect:withParameters:isCancelled:isRasterizing:)] ? 1 : 0); - flags.implementsImageDisplay = ([c respondsToSelector:@selector(displayWithParameters:isCancelled:)] ? 1 : 0); - if (instance) { - flags.implementsDrawParameters = ([instance respondsToSelector:@selector(drawParametersForAsyncLayer:)] ? 1 : 0); - } else { - flags.implementsDrawParameters = ([c instancesRespondToSelector:@selector(drawParametersForAsyncLayer:)] ? 1 : 0); - } - - - return flags; -} - -/** - * Returns ASDisplayNodeMethodOverrides for the given class - * - * @param c the class, required. - * - * @return ASDisplayNodeMethodOverrides. - */ -static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) -{ - ASDisplayNodeCAssertNotNil(c, @"class is required"); - - ASDisplayNodeMethodOverrides overrides = ASDisplayNodeMethodOverrideNone; - - // Handling touches - if (ASDisplayNodeSubclassOverridesSelector(c, @selector(touchesBegan:withEvent:))) { - overrides |= ASDisplayNodeMethodOverrideTouchesBegan; - } - if (ASDisplayNodeSubclassOverridesSelector(c, @selector(touchesMoved:withEvent:))) { - overrides |= ASDisplayNodeMethodOverrideTouchesMoved; - } - if (ASDisplayNodeSubclassOverridesSelector(c, @selector(touchesCancelled:withEvent:))) { - overrides |= ASDisplayNodeMethodOverrideTouchesCancelled; - } - if (ASDisplayNodeSubclassOverridesSelector(c, @selector(touchesEnded:withEvent:))) { - overrides |= ASDisplayNodeMethodOverrideTouchesEnded; - } - - // Responder chain - if (ASDisplayNodeSubclassOverridesSelector(c, @selector(canBecomeFirstResponder))) { - overrides |= ASDisplayNodeMethodOverrideCanBecomeFirstResponder; - } - if (ASDisplayNodeSubclassOverridesSelector(c, @selector(becomeFirstResponder))) { - overrides |= ASDisplayNodeMethodOverrideBecomeFirstResponder; - } - if (ASDisplayNodeSubclassOverridesSelector(c, @selector(canResignFirstResponder))) { - overrides |= ASDisplayNodeMethodOverrideCanResignFirstResponder; - } - if (ASDisplayNodeSubclassOverridesSelector(c, @selector(resignFirstResponder))) { - overrides |= ASDisplayNodeMethodOverrideResignFirstResponder; - } - if (ASDisplayNodeSubclassOverridesSelector(c, @selector(isFirstResponder))) { - overrides |= ASDisplayNodeMethodOverrideIsFirstResponder; - } - - // Layout related methods - if (ASDisplayNodeSubclassOverridesSelector(c, @selector(layoutSpecThatFits:))) { - overrides |= ASDisplayNodeMethodOverrideLayoutSpecThatFits; - } - if (ASDisplayNodeSubclassOverridesSelector(c, @selector(calculateLayoutThatFits:)) || - ASDisplayNodeSubclassOverridesSelector(c, @selector(calculateLayoutThatFits: - restrictedToSize: - relativeToParentSize:))) { - overrides |= ASDisplayNodeMethodOverrideCalcLayoutThatFits; - } - if (ASDisplayNodeSubclassOverridesSelector(c, @selector(calculateSizeThatFits:))) { - overrides |= ASDisplayNodeMethodOverrideCalcSizeThatFits; - } - - return overrides; -} - -+ (void)initialize -{ -#if ASDISPLAYNODE_ASSERTIONS_ENABLED - if (self != [ASDisplayNode class]) { - - // Subclasses should never override these. Use unused to prevent warnings - __unused NSString *classString = NSStringFromClass(self); - - //ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self, @selector(calculatedSize)), @"Subclass %@ must not override calculatedSize method.", classString); - ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self, @selector(calculatedLayout)), @"Subclass %@ must not override calculatedLayout method.", classString); - ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self, @selector(layoutThatFits:)), @"Subclass %@ must not override layoutThatFits: method. Instead override calculateLayoutThatFits:.", classString); - ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self, @selector(layoutThatFits:parentSize:)), @"Subclass %@ must not override layoutThatFits:parentSize method. Instead override calculateLayoutThatFits:.", classString); - ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self, @selector(recursivelyClearContents)), @"Subclass %@ must not override recursivelyClearContents method.", classString); - ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self, @selector(recursivelyClearPreloadedData)), @"Subclass %@ must not override recursivelyClearFetchedData method.", classString); - } else { - // Check if subnodes where modified during the creation of the layout - __block IMP originalLayoutSpecThatFitsIMP = ASReplaceMethodWithBlock(self, @selector(_locked_layoutElementThatFits:), ^(ASDisplayNode *_self, ASSizeRange sizeRange) { - NSArray *oldSubnodes = _self.subnodes; - ASLayoutSpec *layoutElement = ((ASLayoutSpec *( *)(id, SEL, ASSizeRange))originalLayoutSpecThatFitsIMP)(_self, @selector(_locked_layoutElementThatFits:), sizeRange); - NSArray *subnodes = _self.subnodes; - ASDisplayNodeAssert(oldSubnodes.count == subnodes.count, @"Adding or removing nodes in layoutSpecBlock or layoutSpecThatFits: is not allowed and can cause unexpected behavior."); - for (NSInteger i = 0; i < oldSubnodes.count; i++) { - ASDisplayNodeAssert(oldSubnodes[i] == subnodes[i], @"Adding or removing nodes in layoutSpecBlock or layoutSpecThatFits: is not allowed and can cause unexpected behavior."); - } - return layoutElement; - }); - } -#endif - - // Below we are pre-calculating values per-class and dynamically adding a method (_staticInitialize) to populate these values - // when each instance is constructed. These values don't change for each class, so there is significant performance benefit - // in doing it here. +initialize is guaranteed to be called before any instance method so it is safe to add this method here. - // Note that we take care to detect if the class overrides +respondsToSelector: or -respondsToSelector and take the slow path - // (recalculating for each instance) to make sure we are always correct. - - BOOL classOverridesRespondsToSelector = ASSubclassOverridesClassSelector([NSObject class], self, @selector(respondsToSelector:)); - BOOL instancesOverrideRespondsToSelector = ASSubclassOverridesSelector([NSObject class], self, @selector(respondsToSelector:)); - struct ASDisplayNodeFlags flags = GetASDisplayNodeFlags(self, nil); - ASDisplayNodeMethodOverrides methodOverrides = GetASDisplayNodeMethodOverrides(self); - - __unused Class initializeSelf = self; - - IMP staticInitialize = imp_implementationWithBlock(^(ASDisplayNode *node) { - ASDisplayNodeAssert(node.class == initializeSelf, @"Node class %@ does not have a matching _staticInitialize method; check to ensure [super initialize] is called within any custom +initialize implementations! Overridden methods will not be called unless they are also implemented by superclass %@", node.class, initializeSelf); - node->_flags = (classOverridesRespondsToSelector || instancesOverrideRespondsToSelector) ? GetASDisplayNodeFlags(node.class, node) : flags; - node->_methodOverrides = (classOverridesRespondsToSelector) ? GetASDisplayNodeMethodOverrides(node.class) : methodOverrides; - }); - - class_replaceMethod(self, @selector(_staticInitialize), staticInitialize, "v:@"); - - // Add stub implementations for global methods that the client didn't - // implement in a category. We do this instead of hard-coding empty - // implementations to avoid a linker warning when it merges categories. - // Note: addMethod will not do anything if a method already exists. - if (self == ASDisplayNode.class) { - IMP noArgsImp = (IMP)StubImplementationWithNoArgs; - class_addMethod(self, @selector(baseDidInit), noArgsImp, "v@:"); - class_addMethod(self, @selector(baseWillDealloc), noArgsImp, "v@:"); - class_addMethod(self, @selector(didLoad), noArgsImp, "v@:"); - class_addMethod(self, @selector(layoutDidFinish), noArgsImp, "v@:"); - class_addMethod(self, @selector(didEnterPreloadState), noArgsImp, "v@:"); - class_addMethod(self, @selector(didExitPreloadState), noArgsImp, "v@:"); - class_addMethod(self, @selector(didEnterDisplayState), noArgsImp, "v@:"); - class_addMethod(self, @selector(didExitDisplayState), noArgsImp, "v@:"); - class_addMethod(self, @selector(didEnterVisibleState), noArgsImp, "v@:"); - class_addMethod(self, @selector(didExitVisibleState), noArgsImp, "v@:"); - class_addMethod(self, @selector(hierarchyDisplayDidFinish), noArgsImp, "v@:"); - class_addMethod(self, @selector(asyncTraitCollectionDidChange), noArgsImp, "v@:"); - class_addMethod(self, @selector(calculatedLayoutDidChange), noArgsImp, "v@:"); - - auto type0 = "v@:" + std::string(@encode(ASSizeRange)); - class_addMethod(self, @selector(willCalculateLayout:), (IMP)StubImplementationWithSizeRange, type0.c_str()); - - auto interfaceStateType = std::string(@encode(ASInterfaceState)); - auto type1 = "v@:" + interfaceStateType + interfaceStateType; - class_addMethod(self, @selector(interfaceStateDidChange:fromState:), (IMP)StubImplementationWithTwoInterfaceStates, type1.c_str()); - } -} - -#if !AS_INITIALIZE_FRAMEWORK_MANUALLY -+ (void)load -{ - ASInitializeFrameworkMainThread(); -} -#endif - -+ (Class)viewClass -{ - return [_ASDisplayView class]; -} - -+ (Class)layerClass -{ - return [_ASDisplayLayer class]; -} - -#pragma mark - Lifecycle - -- (void)_staticInitialize -{ - ASDisplayNodeAssert(NO, @"_staticInitialize must be overridden"); -} - -- (void)_initializeInstance -{ - [self _staticInitialize]; - -#if ASEVENTLOG_ENABLE - _eventLog = [[ASEventLog alloc] initWithObject:self]; -#endif - - _viewClass = [self.class viewClass]; - _layerClass = [self.class layerClass]; - BOOL isSynchronous = ![_viewClass isSubclassOfClass:[_ASDisplayView class]] - || ![_layerClass isSubclassOfClass:[_ASDisplayLayer class]]; - setFlag(Synchronous, isSynchronous); - - - _contentsScaleForDisplay = ASScreenScale(); - _drawingPriority = ASDefaultTransactionPriority; - - _primitiveTraitCollection = ASPrimitiveTraitCollectionMakeDefault(); - - _layoutVersion = 1; - - _defaultLayoutTransitionDuration = 0.2; - _defaultLayoutTransitionDelay = 0.0; - _defaultLayoutTransitionOptions = UIViewAnimationOptionCurveEaseInOut | UIViewAnimationOptionTransitionNone; - - _flags.canClearContentsOfLayer = YES; - _flags.canCallSetNeedsDisplayOfLayer = YES; - - _fallbackSafeAreaInsets = UIEdgeInsetsZero; - _fallbackInsetsLayoutMarginsFromSafeArea = YES; - _isViewControllerRoot = NO; - - _automaticallyRelayoutOnSafeAreaChanges = NO; - _automaticallyRelayoutOnLayoutMarginsChanges = NO; - - [self baseDidInit]; - ASDisplayNodeLogEvent(self, @"init"); -} - -- (instancetype)init -{ - if (!(self = [super init])) - return nil; - - [self _initializeInstance]; - - return self; -} - -- (instancetype)initWithViewClass:(Class)viewClass -{ - if (!(self = [self init])) - return nil; - - ASDisplayNodeAssert([viewClass isSubclassOfClass:[UIView class]], @"should initialize with a subclass of UIView"); - - _viewClass = viewClass; - setFlag(Synchronous, ![viewClass isSubclassOfClass:[_ASDisplayView class]]); - - return self; -} - -- (instancetype)initWithLayerClass:(Class)layerClass -{ - if (!(self = [self init])) { - return nil; - } - - ASDisplayNodeAssert([layerClass isSubclassOfClass:[CALayer class]], @"should initialize with a subclass of CALayer"); - - _layerClass = layerClass; - _flags.layerBacked = YES; - setFlag(Synchronous, ![layerClass isSubclassOfClass:[_ASDisplayLayer class]]); - - return self; -} - -- (instancetype)initWithViewBlock:(ASDisplayNodeViewBlock)viewBlock -{ - return [self initWithViewBlock:viewBlock didLoadBlock:nil]; -} - -- (instancetype)initWithViewBlock:(ASDisplayNodeViewBlock)viewBlock didLoadBlock:(ASDisplayNodeDidLoadBlock)didLoadBlock -{ - if (!(self = [self init])) { - return nil; - } - - [self setViewBlock:viewBlock]; - if (didLoadBlock != nil) { - [self onDidLoad:didLoadBlock]; - } - - return self; -} - -- (instancetype)initWithLayerBlock:(ASDisplayNodeLayerBlock)layerBlock -{ - return [self initWithLayerBlock:layerBlock didLoadBlock:nil]; -} - -- (instancetype)initWithLayerBlock:(ASDisplayNodeLayerBlock)layerBlock didLoadBlock:(ASDisplayNodeDidLoadBlock)didLoadBlock -{ - if (!(self = [self init])) { - return nil; - } - - [self setLayerBlock:layerBlock]; - if (didLoadBlock != nil) { - [self onDidLoad:didLoadBlock]; - } - - return self; -} - -ASSynthesizeLockingMethodsWithMutex(__instanceLock__); - -- (void)setViewBlock:(ASDisplayNodeViewBlock)viewBlock -{ - ASDisplayNodeAssertFalse(self.nodeLoaded); - ASDisplayNodeAssertNotNil(viewBlock, @"should initialize with a valid block that returns a UIView"); - - _viewBlock = viewBlock; - setFlag(Synchronous, YES); -} - -- (void)setLayerBlock:(ASDisplayNodeLayerBlock)layerBlock -{ - ASDisplayNodeAssertFalse(self.nodeLoaded); - ASDisplayNodeAssertNotNil(layerBlock, @"should initialize with a valid block that returns a CALayer"); - - _layerBlock = layerBlock; - _flags.layerBacked = YES; - setFlag(Synchronous, YES); -} - -- (ASDisplayNodeMethodOverrides)methodOverrides -{ - return _methodOverrides; -} - -- (void)onDidLoad:(ASDisplayNodeDidLoadBlock)body -{ - AS::UniqueLock l(__instanceLock__); - - if ([self _locked_isNodeLoaded]) { - ASDisplayNodeAssertThreadAffinity(self); - l.unlock(); - body(self); - return; - } else if (_onDidLoadBlocks == nil) { - _onDidLoadBlocks = [NSMutableArray arrayWithObject:body]; - } else { - [_onDidLoadBlocks addObject:body]; - } -} - -- (void)dealloc -{ - _flags.isDeallocating = YES; - [self baseWillDealloc]; - - // Synchronous nodes may not be able to call the hierarchy notifications, so only enforce for regular nodes. - //ASDisplayNodeAssert(checkFlag(Synchronous) || !ASInterfaceStateIncludesVisible(_interfaceState), @"Node should always be marked invisible before deallocating. Node: %@", self); - - self.asyncLayer.asyncDelegate = nil; - _view.asyncdisplaykit_node = nil; - _layer.asyncdisplaykit_node = nil; - - // Remove any subnodes so they lose their connection to the now deallocated parent. This can happen - // because subnodes do not retain their supernode, but subnodes can legitimately remain alive if another - // thing outside the view hierarchy system (e.g. async display, controller code, etc). keeps a retained - // reference to subnodes. - - for (ASDisplayNode *subnode in _subnodes) - [subnode _setSupernode:nil]; - - [self scheduleIvarsForMainThreadDeallocation]; - - // TODO: Remove this? If supernode isn't already nil, this method isn't dealloc-safe anyway. - [self _setSupernode:nil]; -} - -#pragma mark - Loading - -- (BOOL)_locked_shouldLoadViewOrLayer -{ - ASAssertLocked(__instanceLock__); - return !_flags.isDeallocating && !(_hierarchyState & ASHierarchyStateRasterized); -} - -- (UIView *)_locked_viewToLoad -{ - ASAssertLocked(__instanceLock__); - - UIView *view = nil; - if (_viewBlock) { - view = _viewBlock(); - ASDisplayNodeAssertNotNil(view, @"View block returned nil"); - ASDisplayNodeAssert(![view isKindOfClass:[_ASDisplayView class]], @"View block should return a synchronously displayed view"); - _viewBlock = nil; - _viewClass = [view class]; - } else { - view = [[_viewClass alloc] init]; - } - - // Special handling of wrapping UIKit components - if (checkFlag(Synchronous)) { - [self checkResponderCompatibility]; - - // UIImageView layers. More details on the flags - if ([_viewClass isSubclassOfClass:[UIImageView class]]) { - _flags.canClearContentsOfLayer = NO; - _flags.canCallSetNeedsDisplayOfLayer = NO; - } - - // UIActivityIndicator - if ([_viewClass isSubclassOfClass:[UIActivityIndicatorView class]] - || [_viewClass isSubclassOfClass:[UIVisualEffectView class]]) { - self.opaque = NO; - } - - // CAEAGLLayer - if([[view.layer class] isSubclassOfClass:[CAEAGLLayer class]]){ - _flags.canClearContentsOfLayer = NO; - } - } - - return view; -} - -- (CALayer *)_locked_layerToLoad -{ - ASAssertLocked(__instanceLock__); - ASDisplayNodeAssert(_flags.layerBacked, @"_layerToLoad is only for layer-backed nodes"); - - CALayer *layer = nil; - if (_layerBlock) { - layer = _layerBlock(); - ASDisplayNodeAssertNotNil(layer, @"Layer block returned nil"); - ASDisplayNodeAssert(![layer isKindOfClass:[_ASDisplayLayer class]], @"Layer block should return a synchronously displayed layer"); - _layerBlock = nil; - _layerClass = [layer class]; - } else { - layer = [[_layerClass alloc] init]; - } - - return layer; -} - -- (void)_locked_loadViewOrLayer -{ - ASAssertLocked(__instanceLock__); - - if (_flags.layerBacked) { - TIME_SCOPED(_debugTimeToCreateView); - _layer = [self _locked_layerToLoad]; - static int ASLayerDelegateAssociationKey; - - /** - * CALayer's .delegate property is documented to be weak, but the implementation is actually assign. - * Because our layer may survive longer than the node (e.g. if someone else retains it, or if the node - * begins deallocation on a background thread and it waiting for the -dealloc call to reach main), the only - * way to avoid a dangling pointer is to use a weak proxy. - */ - ASWeakProxy *instance = [ASWeakProxy weakProxyWithTarget:self]; - _layer.delegate = (id)instance; - objc_setAssociatedObject(_layer, &ASLayerDelegateAssociationKey, instance, OBJC_ASSOCIATION_RETAIN_NONATOMIC); - } else { - TIME_SCOPED(_debugTimeToCreateView); - _view = [self _locked_viewToLoad]; - _view.asyncdisplaykit_node = self; - _layer = _view.layer; - } - _layer.asyncdisplaykit_node = self; - - self._locked_asyncLayer.asyncDelegate = self; -} - -- (void)_didLoad -{ - ASDisplayNodeAssertMainThread(); - ASAssertUnlocked(__instanceLock__); - ASDisplayNodeLogEvent(self, @"didLoad"); - TIME_SCOPED(_debugTimeForDidLoad); - - [self didLoad]; - - __instanceLock__.lock(); - const auto onDidLoadBlocks = ASTransferStrong(_onDidLoadBlocks); - __instanceLock__.unlock(); - - for (ASDisplayNodeDidLoadBlock block in onDidLoadBlocks) { - block(self); - } - [self enumerateInterfaceStateDelegates:^(id del) { - [del nodeDidLoad]; - }]; -} - -- (BOOL)isNodeLoaded -{ - if (ASDisplayNodeThreadIsMain()) { - // Because the view and layer can only be created and destroyed on Main, that is also the only thread - // where the state of this property can change. As an optimization, we can avoid locking. - return _loaded(self); - } else { - MutexLocker l(__instanceLock__); - return [self _locked_isNodeLoaded]; - } -} - -- (BOOL)_locked_isNodeLoaded -{ - ASAssertLocked(__instanceLock__); - return _loaded(self); -} - -#pragma mark - Misc Setter / Getter - -- (UIView *)view -{ - AS::UniqueLock l(__instanceLock__); - - ASDisplayNodeAssert(!_flags.layerBacked, @"Call to -view undefined on layer-backed nodes"); - BOOL isLayerBacked = _flags.layerBacked; - if (isLayerBacked) { - return nil; - } - - if (_view != nil) { - return _view; - } - - if (![self _locked_shouldLoadViewOrLayer]) { - return nil; - } - - // Loading a view needs to happen on the main thread - ASDisplayNodeAssertMainThread(); - [self _locked_loadViewOrLayer]; - - // FIXME: Ideally we'd call this as soon as the node receives -setNeedsLayout - // but automatic subnode management would require us to modify the node tree - // in the background on a loaded node, which isn't currently supported. - if (_pendingViewState.hasSetNeedsLayout) { - // Need to unlock before calling setNeedsLayout to avoid deadlocks. - l.unlock(); - [self __setNeedsLayout]; - l.lock(); - } - - [self _locked_applyPendingStateToViewOrLayer]; - - // The following methods should not be called with a lock - l.unlock(); - - // No need for the lock as accessing the subviews or layers are always happening on main - [self _addSubnodeViewsAndLayers]; - - // A subclass hook should never be called with a lock - [self _didLoad]; - - return _view; -} - -- (CALayer *)layer -{ - AS::UniqueLock l(__instanceLock__); - if (_layer != nil) { - return _layer; - } - - if (![self _locked_shouldLoadViewOrLayer]) { - return nil; - } - - // Loading a layer needs to happen on the main thread - ASDisplayNodeAssertMainThread(); - [self _locked_loadViewOrLayer]; - CALayer *layer = _layer; - - // FIXME: Ideally we'd call this as soon as the node receives -setNeedsLayout - // but automatic subnode management would require us to modify the node tree - // in the background on a loaded node, which isn't currently supported. - if (_pendingViewState.hasSetNeedsLayout) { - // Need to unlock before calling setNeedsLayout to avoid deadlocks. - l.unlock(); - [self __setNeedsLayout]; - l.lock(); - } - - [self _locked_applyPendingStateToViewOrLayer]; - - // The following methods should not be called with a lock - l.unlock(); - - // No need for the lock as accessing the subviews or layers are always happening on main - [self _addSubnodeViewsAndLayers]; - - // A subclass hook should never be called with a lock - [self _didLoad]; - - return layer; -} - -// Returns nil if the layer is not an _ASDisplayLayer; will not create the layer if nil. -- (_ASDisplayLayer *)asyncLayer -{ - MutexLocker l(__instanceLock__); - return [self _locked_asyncLayer]; -} - -- (_ASDisplayLayer *)_locked_asyncLayer -{ - ASAssertLocked(__instanceLock__); - return [_layer isKindOfClass:[_ASDisplayLayer class]] ? (_ASDisplayLayer *)_layer : nil; -} - -- (BOOL)isSynchronous -{ - return checkFlag(Synchronous); -} - -- (void)setLayerBacked:(BOOL)isLayerBacked -{ - // Only call this if assertions are enabled – it could be expensive. - ASDisplayNodeAssert(!isLayerBacked || self.supportsLayerBacking, @"Node %@ does not support layer backing.", self); - - MutexLocker l(__instanceLock__); - if (_flags.layerBacked == isLayerBacked) { - return; - } - - if ([self _locked_isNodeLoaded]) { - ASDisplayNodeFailAssert(@"Cannot change layerBacked after view/layer has loaded."); - return; - } - - _flags.layerBacked = isLayerBacked; -} - -- (BOOL)isLayerBacked -{ - MutexLocker l(__instanceLock__); - return _flags.layerBacked; -} - -- (BOOL)supportsLayerBacking -{ - MutexLocker l(__instanceLock__); - return !checkFlag(Synchronous) && !_flags.viewEverHadAGestureRecognizerAttached && _viewClass == [_ASDisplayView class] && _layerClass == [_ASDisplayLayer class]; -} - -- (BOOL)shouldAnimateSizeChanges -{ - MutexLocker l(__instanceLock__); - return _flags.shouldAnimateSizeChanges; -} - -- (void)setShouldAnimateSizeChanges:(BOOL)shouldAnimateSizeChanges -{ - MutexLocker l(__instanceLock__); - _flags.shouldAnimateSizeChanges = shouldAnimateSizeChanges; -} - -- (CGRect)threadSafeBounds -{ - MutexLocker l(__instanceLock__); - return [self _locked_threadSafeBounds]; -} - -- (CGRect)_locked_threadSafeBounds -{ - ASAssertLocked(__instanceLock__); - return _threadSafeBounds; -} - -- (void)setThreadSafeBounds:(CGRect)newBounds -{ - MutexLocker l(__instanceLock__); - _threadSafeBounds = newBounds; -} - -- (void)nodeViewDidAddGestureRecognizer -{ - MutexLocker l(__instanceLock__); - _flags.viewEverHadAGestureRecognizerAttached = YES; -} - -- (UIEdgeInsets)fallbackSafeAreaInsets -{ - MutexLocker l(__instanceLock__); - return _fallbackSafeAreaInsets; -} - -- (void)setFallbackSafeAreaInsets:(UIEdgeInsets)insets -{ - BOOL needsManualUpdate; - BOOL updatesLayoutMargins; - - { - MutexLocker l(__instanceLock__); - ASDisplayNodeAssertThreadAffinity(self); - - if (UIEdgeInsetsEqualToEdgeInsets(insets, _fallbackSafeAreaInsets)) { - return; - } - - _fallbackSafeAreaInsets = insets; - needsManualUpdate = !AS_AT_LEAST_IOS11 || _flags.layerBacked; - updatesLayoutMargins = needsManualUpdate && [self _locked_insetsLayoutMarginsFromSafeArea]; - } - - if (needsManualUpdate) { - [self safeAreaInsetsDidChange]; - } - - if (updatesLayoutMargins) { - [self layoutMarginsDidChange]; - } -} - -- (void)_fallbackUpdateSafeAreaOnChildren -{ - ASDisplayNodeAssertThreadAffinity(self); - - UIEdgeInsets insets = self.safeAreaInsets; - CGRect bounds = self.bounds; - - for (ASDisplayNode *child in self.subnodes) { - if (AS_AT_LEAST_IOS11 && !child.layerBacked) { - // In iOS 11 view-backed nodes already know what their safe area is. - continue; - } - - if (child.viewControllerRoot) { - // Its safe area is controlled by a view controller. Don't override it. - continue; - } - - CGRect childFrame = child.frame; - UIEdgeInsets childInsets = UIEdgeInsetsMake(MAX(insets.top - (CGRectGetMinY(childFrame) - CGRectGetMinY(bounds)), 0), - MAX(insets.left - (CGRectGetMinX(childFrame) - CGRectGetMinX(bounds)), 0), - MAX(insets.bottom - (CGRectGetMaxY(bounds) - CGRectGetMaxY(childFrame)), 0), - MAX(insets.right - (CGRectGetMaxX(bounds) - CGRectGetMaxX(childFrame)), 0)); - - child.fallbackSafeAreaInsets = childInsets; - } -} - -- (BOOL)isViewControllerRoot -{ - MutexLocker l(__instanceLock__); - return _isViewControllerRoot; -} - -- (void)setViewControllerRoot:(BOOL)flag -{ - MutexLocker l(__instanceLock__); - _isViewControllerRoot = flag; -} - -- (BOOL)automaticallyRelayoutOnSafeAreaChanges -{ - MutexLocker l(__instanceLock__); - return _automaticallyRelayoutOnSafeAreaChanges; -} - -- (void)setAutomaticallyRelayoutOnSafeAreaChanges:(BOOL)flag -{ - MutexLocker l(__instanceLock__); - _automaticallyRelayoutOnSafeAreaChanges = flag; -} - -- (BOOL)automaticallyRelayoutOnLayoutMarginsChanges -{ - MutexLocker l(__instanceLock__); - return _automaticallyRelayoutOnLayoutMarginsChanges; -} - -- (void)setAutomaticallyRelayoutOnLayoutMarginsChanges:(BOOL)flag -{ - MutexLocker l(__instanceLock__); - _automaticallyRelayoutOnLayoutMarginsChanges = flag; -} - -#pragma mark - UIResponder - -#define HANDLE_NODE_RESPONDER_METHOD(__sel) \ - /* All responder methods should be called on the main thread */ \ - ASDisplayNodeAssertMainThread(); \ - if (checkFlag(Synchronous)) { \ - /* If the view is not a _ASDisplayView subclass (Synchronous) just call through to the view as we - expect it's a non _ASDisplayView subclass that will respond */ \ - return [_view __sel]; \ - } else { \ - if (ASSubclassOverridesSelector([_ASDisplayView class], _viewClass, @selector(__sel))) { \ - /* If the subclass overwrites canBecomeFirstResponder just call through - to it as we expect it will handle it */ \ - return [_view __sel]; \ - } else { \ - /* Call through to _ASDisplayView's superclass to get it handled */ \ - return [(_ASDisplayView *)_view __##__sel]; \ - } \ - } \ - -- (void)checkResponderCompatibility -{ -#if ASDISPLAYNODE_ASSERTIONS_ENABLED - // There are certain cases we cannot handle and are not supported: - // 1. If the _view class is not a subclass of _ASDisplayView - if (checkFlag(Synchronous)) { - // 2. At least one UIResponder methods are overwritten in the node subclass - NSString *message = @"Overwritting %@ and having a backing view that is not an _ASDisplayView is not supported."; - ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self.class, @selector(canBecomeFirstResponder)), ([NSString stringWithFormat:message, @"canBecomeFirstResponder"])); - ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self.class, @selector(becomeFirstResponder)), ([NSString stringWithFormat:message, @"becomeFirstResponder"])); - ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self.class, @selector(canResignFirstResponder)), ([NSString stringWithFormat:message, @"canResignFirstResponder"])); - ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self.class, @selector(resignFirstResponder)), ([NSString stringWithFormat:message, @"resignFirstResponder"])); - ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self.class, @selector(isFirstResponder)), ([NSString stringWithFormat:message, @"isFirstResponder"])); - } -#endif -} - -- (BOOL)__canBecomeFirstResponder -{ - if (_view == nil) { - // By default we return NO if not view is created yet - return NO; - } - - HANDLE_NODE_RESPONDER_METHOD(canBecomeFirstResponder); -} - -- (BOOL)__becomeFirstResponder -{ - // Note: This implicitly loads the view if it hasn't been loaded yet. - [self view]; - - if (![self canBecomeFirstResponder]) { - return NO; - } - - HANDLE_NODE_RESPONDER_METHOD(becomeFirstResponder); -} - -- (BOOL)__canResignFirstResponder -{ - if (_view == nil) { - // By default we return YES if no view is created yet - return YES; - } - - HANDLE_NODE_RESPONDER_METHOD(canResignFirstResponder); -} - -- (BOOL)__resignFirstResponder -{ - // Note: This implicitly loads the view if it hasn't been loaded yet. - [self view]; - - if (![self canResignFirstResponder]) { - return NO; - } - - HANDLE_NODE_RESPONDER_METHOD(resignFirstResponder); -} - -- (BOOL)__isFirstResponder -{ - if (_view == nil) { - // If no view is created yet we can just return NO as it's unlikely it's the first responder - return NO; - } - - HANDLE_NODE_RESPONDER_METHOD(isFirstResponder); -} - -#pragma mark - -- (NSString *)debugName -{ - MutexLocker l(__instanceLock__); - return _debugName; -} - -- (void)setDebugName:(NSString *)debugName -{ - MutexLocker l(__instanceLock__); - if (!ASObjectIsEqual(_debugName, debugName)) { - _debugName = [debugName copy]; - } -} - -#pragma mark - Layout - -#pragma mark - -- (BOOL)canLayoutAsynchronous -{ - return !self.isNodeLoaded; -} - -#pragma mark Layout Pass - -- (void)__setNeedsLayout -{ - [self invalidateCalculatedLayout]; -} - -- (void)invalidateCalculatedLayout -{ - MutexLocker l(__instanceLock__); - - _layoutVersion++; - - _unflattenedLayout = nil; - -#if YOGA - [self invalidateCalculatedYogaLayout]; -#endif -} - -- (void)__layout -{ - ASDisplayNodeAssertThreadAffinity(self); - // ASAssertUnlocked(__instanceLock__); - - BOOL loaded = NO; - { - AS::UniqueLock l(__instanceLock__); - loaded = [self _locked_isNodeLoaded]; - CGRect bounds = _threadSafeBounds; - - if (CGRectEqualToRect(bounds, CGRectZero)) { - // Performing layout on a zero-bounds view often results in frame calculations - // with negative sizes after applying margins, which will cause - // layoutThatFits: on subnodes to assert. - return; - } - - // If a current layout transition is in progress there is no need to do a measurement and layout pass in here as - // this is supposed to happen within the layout transition process - if (_transitionID != ASLayoutElementContextInvalidTransitionID) { - return; - } - - // This method will confirm that the layout is up to date (and update if needed). - // Importantly, it will also APPLY the layout to all of our subnodes if (unless parent is transitioning). - l.unlock(); - [self _u_measureNodeWithBoundsIfNecessary:bounds]; - l.lock(); - - [self _locked_layoutPlaceholderIfNecessary]; - } - - [self _layoutSublayouts]; - - // Per API contract, `-layout` and `-layoutDidFinish` are called only if the node is loaded. - if (loaded) { - ASPerformBlockOnMainThread(^{ - [self layout]; - [self _layoutClipCornersIfNeeded]; - [self _layoutDidFinish]; - }); - } - - [self _fallbackUpdateSafeAreaOnChildren]; -} - -- (void)_layoutDidFinish -{ - ASDisplayNodeAssertMainThread(); - // ASAssertUnlocked(__instanceLock__); - ASDisplayNodeAssertTrue(self.isNodeLoaded); - [self layoutDidFinish]; -} - -#pragma mark Calculation - -- (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize - restrictedToSize:(ASLayoutElementSize)size - relativeToParentSize:(CGSize)parentSize -{ -#if AS_KDEBUG_ENABLE - // We only want one calculateLayout signpost interval per thread. - // Currently there is no fallback for profiling i386, since it's not useful. - static _Thread_local NSInteger tls_callDepth; - if (tls_callDepth++ == 0) { - ASSignpostStart(ASSignpostCalculateLayout); - } -#endif - - ASSizeRange styleAndParentSize = ASLayoutElementSizeResolve(self.style.size, parentSize); - const ASSizeRange resolvedRange = ASSizeRangeIntersect(constrainedSize, styleAndParentSize); - ASLayout *result = [self calculateLayoutThatFits:resolvedRange]; - -#if AS_KDEBUG_ENABLE - if (--tls_callDepth == 0) { - ASSignpostEnd(ASSignpostCalculateLayout); - } -#endif - - return result; -} - -- (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize -{ - __ASDisplayNodeCheckForLayoutMethodOverrides; - - switch (self.layoutEngineType) { - case ASLayoutEngineTypeLayoutSpec: - return [self calculateLayoutLayoutSpec:constrainedSize]; -#if YOGA - case ASLayoutEngineTypeYoga: - return [self calculateLayoutYoga:constrainedSize]; -#endif - // If YOGA is not defined but for some reason the layout type engine is Yoga - // we explicitly fallthrough here - default: - break; - } - - // If this case is reached a layout type engine was defined for a node that is currently - // not supported. - ASDisplayNodeAssert(NO, @"No layout type determined"); - return nil; -} - -- (CGSize)calculateSizeThatFits:(CGSize)constrainedSize -{ - __ASDisplayNodeCheckForLayoutMethodOverrides; - - ASDisplayNodeLogEvent(self, @"calculateSizeThatFits: with constrainedSize: %@", NSStringFromCGSize(constrainedSize)); - - return ASIsCGSizeValidForSize(constrainedSize) ? constrainedSize : CGSizeZero; -} - -- (void)layout -{ - // Hook for subclasses - ASDisplayNodeAssertMainThread(); - // ASAssertUnlocked(__instanceLock__); - ASDisplayNodeAssertTrue(self.isNodeLoaded); - [self enumerateInterfaceStateDelegates:^(id del) { - [del nodeDidLayout]; - }]; -} - -#pragma mark Layout Transition - -- (void)_layoutTransitionMeasurementDidFinish -{ - // Hook for subclasses - No-Op in ASDisplayNode -} - -#pragma mark <_ASTransitionContextCompletionDelegate> - -/** - * After completeTransition: is called on the ASContextTransitioning object in animateLayoutTransition: this - * delegate method will be called that start the completion process of the transition - */ -- (void)transitionContext:(_ASTransitionContext *)context didComplete:(BOOL)didComplete -{ - ASDisplayNodeAssertMainThread(); - - [self didCompleteLayoutTransition:context]; - - _pendingLayoutTransitionContext = nil; - - [self _pendingLayoutTransitionDidComplete]; -} - -#pragma mark - Display - -NSString * const ASRenderingEngineDidDisplayScheduledNodesNotification = @"ASRenderingEngineDidDisplayScheduledNodes"; -NSString * const ASRenderingEngineDidDisplayNodesScheduledBeforeTimestamp = @"ASRenderingEngineDidDisplayNodesScheduledBeforeTimestamp"; - -- (BOOL)displaysAsynchronously -{ - MutexLocker l(__instanceLock__); - return [self _locked_displaysAsynchronously]; -} - -/** - * Core implementation of -displaysAsynchronously. - */ -- (BOOL)_locked_displaysAsynchronously -{ - ASAssertLocked(__instanceLock__); - return checkFlag(Synchronous) == NO && _flags.displaysAsynchronously; -} - -- (void)setDisplaysAsynchronously:(BOOL)displaysAsynchronously -{ - ASDisplayNodeAssertThreadAffinity(self); - - MutexLocker l(__instanceLock__); - - // Can't do this for synchronous nodes (using layers that are not _ASDisplayLayer and so we can't control display prevention/cancel) - if (checkFlag(Synchronous)) { - return; - } - - if (_flags.displaysAsynchronously == displaysAsynchronously) { - return; - } - - _flags.displaysAsynchronously = displaysAsynchronously; - - self._locked_asyncLayer.displaysAsynchronously = displaysAsynchronously; -} - -- (BOOL)rasterizesSubtree -{ - MutexLocker l(__instanceLock__); - return _flags.rasterizesSubtree; -} - -- (void)enableSubtreeRasterization -{ - MutexLocker l(__instanceLock__); - // Already rasterized from self. - if (_flags.rasterizesSubtree) { - return; - } - - // If rasterized from above, bail. - if (ASHierarchyStateIncludesRasterized(_hierarchyState)) { - ASDisplayNodeFailAssert(@"Subnode of a rasterized node should not have redundant -enableSubtreeRasterization."); - return; - } - - // Ensure not loaded. - if ([self _locked_isNodeLoaded]) { - ASDisplayNodeFailAssert(@"Cannot call %@ on loaded node: %@", NSStringFromSelector(_cmd), self); - return; - } - - // Ensure no loaded subnodes - ASDisplayNode *loadedSubnode = ASDisplayNodeFindFirstSubnode(self, ^BOOL(ASDisplayNode * _Nonnull node) { - return node.nodeLoaded; - }); - if (loadedSubnode != nil) { - ASDisplayNodeFailAssert(@"Cannot call %@ on node %@ with loaded subnode %@", NSStringFromSelector(_cmd), self, loadedSubnode); - return; - } - - _flags.rasterizesSubtree = YES; - - // Tell subnodes that now they're in a rasterized hierarchy (while holding lock!) - for (ASDisplayNode *subnode in _subnodes) { - [subnode enterHierarchyState:ASHierarchyStateRasterized]; - } -} - -- (CGFloat)contentsScaleForDisplay -{ - MutexLocker l(__instanceLock__); - - return _contentsScaleForDisplay; -} - -- (void)setContentsScaleForDisplay:(CGFloat)contentsScaleForDisplay -{ - MutexLocker l(__instanceLock__); - - if (_contentsScaleForDisplay == contentsScaleForDisplay) { - return; - } - - _contentsScaleForDisplay = contentsScaleForDisplay; -} - -- (void)displayImmediately -{ - ASDisplayNodeAssertMainThread(); - ASDisplayNodeAssert(!checkFlag(Synchronous), @"this method is designed for asynchronous mode only"); - - [self.asyncLayer displayImmediately]; -} - -- (void)recursivelyDisplayImmediately -{ - for (ASDisplayNode *child in self.subnodes) { - [child recursivelyDisplayImmediately]; - } - [self displayImmediately]; -} - -- (void)__setNeedsDisplay -{ - BOOL shouldScheduleForDisplay = NO; - { - MutexLocker l(__instanceLock__); - BOOL nowDisplay = ASInterfaceStateIncludesDisplay(_interfaceState); - // FIXME: This should not need to recursively display, so create a non-recursive variant. - // The semantics of setNeedsDisplay (as defined by CALayer behavior) are not recursive. - if (_layer != nil && !checkFlag(Synchronous) && nowDisplay && [self _implementsDisplay]) { - shouldScheduleForDisplay = YES; - } - } - - if (shouldScheduleForDisplay) { - [ASDisplayNode scheduleNodeForRecursiveDisplay:self]; - } -} - -+ (void)scheduleNodeForRecursiveDisplay:(ASDisplayNode *)node -{ - static dispatch_once_t onceToken; - static ASRunLoopQueue *renderQueue; - dispatch_once(&onceToken, ^{ - renderQueue = [[ASRunLoopQueue alloc] initWithRunLoop:CFRunLoopGetMain() - retainObjects:NO - handler:^(ASDisplayNode * _Nonnull dequeuedItem, BOOL isQueueDrained) { - [dequeuedItem _recursivelyTriggerDisplayAndBlock:NO]; - if (isQueueDrained) { - CFTimeInterval timestamp = CACurrentMediaTime(); - [[NSNotificationCenter defaultCenter] postNotificationName:ASRenderingEngineDidDisplayScheduledNodesNotification - object:nil - userInfo:@{ASRenderingEngineDidDisplayNodesScheduledBeforeTimestamp: @(timestamp)}]; - } - }]; - }); - - [renderQueue enqueue:node]; -} - -/// Helper method to summarize whether or not the node run through the display process -- (BOOL)_implementsDisplay -{ - MutexLocker l(__instanceLock__); - - return _flags.implementsDrawRect || _flags.implementsImageDisplay || _flags.rasterizesSubtree; -} - -// Track that a node will be displayed as part of the current node hierarchy. -// The node sending the message should usually be passed as the parameter, similar to the delegation pattern. -- (void)_pendingNodeWillDisplay:(ASDisplayNode *)node -{ - ASDisplayNodeAssertMainThread(); - - // No lock needed as _pendingDisplayNodes is main thread only - if (!_pendingDisplayNodes) { - _pendingDisplayNodes = [[ASWeakSet alloc] init]; - } - - [_pendingDisplayNodes addObject:node]; -} - -// Notify that a node that was pending display finished -// The node sending the message should usually be passed as the parameter, similar to the delegation pattern. -- (void)_pendingNodeDidDisplay:(ASDisplayNode *)node -{ - ASDisplayNodeAssertMainThread(); - - // No lock for _pendingDisplayNodes needed as it's main thread only - [_pendingDisplayNodes removeObject:node]; - - if (_pendingDisplayNodes.isEmpty) { - - [self hierarchyDisplayDidFinish]; - [self enumerateInterfaceStateDelegates:^(id delegate) { - [delegate hierarchyDisplayDidFinish]; - }]; - - BOOL placeholderShouldPersist = [self placeholderShouldPersist]; - - __instanceLock__.lock(); - if (_placeholderLayer.superlayer && !placeholderShouldPersist) { - void (^cleanupBlock)() = ^{ - [_placeholderLayer removeFromSuperlayer]; - }; - - if (_placeholderFadeDuration > 0.0 && ASInterfaceStateIncludesVisible(self.interfaceState)) { - [CATransaction begin]; - [CATransaction setCompletionBlock:cleanupBlock]; - [CATransaction setAnimationDuration:_placeholderFadeDuration]; - _placeholderLayer.opacity = 0.0; - [CATransaction commit]; - } else { - cleanupBlock(); - } - } - __instanceLock__.unlock(); - } -} - -// Helper method to determine if it's safe to call setNeedsDisplay on a layer without throwing away the content. -// For details look at the comment on the canCallSetNeedsDisplayOfLayer flag -- (BOOL)_canCallSetNeedsDisplayOfLayer -{ - MutexLocker l(__instanceLock__); - return _flags.canCallSetNeedsDisplayOfLayer; -} - -void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) -{ - // This recursion must handle layers in various states: - // 1. Just added to hierarchy, CA hasn't yet called -display - // 2. Previously in a hierarchy (such as a working window owned by an Intelligent Preloading class, like ASTableView / ASCollectionView / ASViewController) - // 3. Has no content to display at all - // Specifically for case 1), we need to explicitly trigger a -display call now. - // Otherwise, there is no opportunity to block the main thread after CoreAnimation's transaction commit - // (even a runloop observer at a late call order will not stop the next frame from compositing, showing placeholders). - - ASDisplayNode *node = [layer asyncdisplaykit_node]; - - if (node.isSynchronous && [node _canCallSetNeedsDisplayOfLayer]) { - // Layers for UIKit components that are wrapped within a node needs to be set to be displayed as the contents of - // the layer get's cleared and would not be recreated otherwise. - // We do not call this for _ASDisplayLayer as an optimization. - [layer setNeedsDisplay]; - } - - if ([node _implementsDisplay]) { - // For layers that do get displayed here, this immediately kicks off the work on the concurrent -[_ASDisplayLayer displayQueue]. - // At the same time, it creates an associated _ASAsyncTransaction, which we can use to block on display completion. See ASDisplayNode+AsyncDisplay.mm. - [layer displayIfNeeded]; - } - - // Kick off the recursion first, so that all necessary display calls are sent and the displayQueue is full of parallelizable work. - // NOTE: The docs report that `sublayers` returns a copy but it actually doesn't. - for (CALayer *sublayer in [layer.sublayers copy]) { - recursivelyTriggerDisplayForLayer(sublayer, shouldBlock); - } - - if (shouldBlock) { - // As the recursion unwinds, verify each transaction is complete and block if it is not. - // While blocking on one transaction, others may be completing concurrently, so it doesn't matter which blocks first. - BOOL waitUntilComplete = (!node.shouldBypassEnsureDisplay); - if (waitUntilComplete) { - for (_ASAsyncTransaction *transaction in [layer.asyncdisplaykit_asyncLayerTransactions copy]) { - // Even if none of the layers have had a chance to start display earlier, they will still be allowed to saturate a multicore CPU while blocking main. - // This significantly reduces time on the main thread relative to UIKit. - [transaction waitUntilComplete]; - } - } - } -} - -- (void)_recursivelyTriggerDisplayAndBlock:(BOOL)shouldBlock -{ - ASDisplayNodeAssertMainThread(); - - CALayer *layer = self.layer; - // -layoutIfNeeded is recursive, and even walks up to superlayers to check if they need layout, - // so we should call it outside of starting the recursion below. If our own layer is not marked - // as dirty, we can assume layout has run on this subtree before. - if ([layer needsLayout]) { - [layer layoutIfNeeded]; - } - recursivelyTriggerDisplayForLayer(layer, shouldBlock); -} - -- (void)recursivelyEnsureDisplaySynchronously:(BOOL)synchronously -{ - [self _recursivelyTriggerDisplayAndBlock:synchronously]; -} - -- (void)setShouldBypassEnsureDisplay:(BOOL)shouldBypassEnsureDisplay -{ - MutexLocker l(__instanceLock__); - _flags.shouldBypassEnsureDisplay = shouldBypassEnsureDisplay; -} - -- (BOOL)shouldBypassEnsureDisplay -{ - MutexLocker l(__instanceLock__); - return _flags.shouldBypassEnsureDisplay; -} - -- (void)setNeedsDisplayAtScale:(CGFloat)contentsScale -{ - { - MutexLocker l(__instanceLock__); - if (contentsScale == _contentsScaleForDisplay) { - return; - } - - _contentsScaleForDisplay = contentsScale; - } - - [self setNeedsDisplay]; -} - -- (void)recursivelySetNeedsDisplayAtScale:(CGFloat)contentsScale -{ - ASDisplayNodePerformBlockOnEveryNode(nil, self, YES, ^(ASDisplayNode *node) { - [node setNeedsDisplayAtScale:contentsScale]; - }); -} - -- (void)_layoutClipCornersIfNeeded -{ - ASDisplayNodeAssertMainThread(); - if (_clipCornerLayers[0] == nil) { - return; - } - - CGSize boundsSize = self.bounds.size; - for (int idx = 0; idx < NUM_CLIP_CORNER_LAYERS; idx++) { - BOOL isTop = (idx == 0 || idx == 1); - BOOL isRight = (idx == 1 || idx == 2); - if (_clipCornerLayers[idx]) { - // Note the Core Animation coordinates are reversed for y; 0 is at the bottom. - _clipCornerLayers[idx].position = CGPointMake(isRight ? boundsSize.width : 0.0, isTop ? boundsSize.height : 0.0); - [_layer addSublayer:_clipCornerLayers[idx]]; - } - } -} - -- (void)_updateClipCornerLayerContentsWithRadius:(CGFloat)radius backgroundColor:(UIColor *)backgroundColor -{ - ASPerformBlockOnMainThread(^{ - for (int idx = 0; idx < NUM_CLIP_CORNER_LAYERS; idx++) { - // Layers are, in order: Top Left, Top Right, Bottom Right, Bottom Left. - // anchorPoint is Bottom Left at 0,0 and Top Right at 1,1. - BOOL isTop = (idx == 0 || idx == 1); - BOOL isRight = (idx == 1 || idx == 2); - - CGSize size = CGSizeMake(radius + 1, radius + 1); - ASGraphicsBeginImageContextWithOptions(size, NO, self.contentsScaleForDisplay); - - CGContextRef ctx = UIGraphicsGetCurrentContext(); - if (isRight == YES) { - CGContextTranslateCTM(ctx, -radius + 1, 0); - } - if (isTop == YES) { - CGContextTranslateCTM(ctx, 0, -radius + 1); - } - UIBezierPath *roundedRect = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, 0, radius * 2, radius * 2) cornerRadius:radius]; - [roundedRect setUsesEvenOddFillRule:YES]; - [roundedRect appendPath:[UIBezierPath bezierPathWithRect:CGRectMake(-1, -1, radius * 2 + 1, radius * 2 + 1)]]; - [backgroundColor setFill]; - [roundedRect fill]; - - // No lock needed, as _clipCornerLayers is only modified on the main thread. - CALayer *clipCornerLayer = _clipCornerLayers[idx]; - clipCornerLayer.contents = (id)(ASGraphicsGetImageAndEndCurrentContext().CGImage); - clipCornerLayer.bounds = CGRectMake(0.0, 0.0, size.width, size.height); - clipCornerLayer.anchorPoint = CGPointMake(isRight ? 1.0 : 0.0, isTop ? 1.0 : 0.0); - } - [self _layoutClipCornersIfNeeded]; - }); -} - -- (void)_setClipCornerLayersVisible:(BOOL)visible -{ -} - -- (void)updateCornerRoundingWithType:(ASCornerRoundingType)newRoundingType cornerRadius:(CGFloat)newCornerRadius -{ - __instanceLock__.lock(); - CGFloat oldCornerRadius = _cornerRadius; - ASCornerRoundingType oldRoundingType = _cornerRoundingType; - - _cornerRadius = newCornerRadius; - _cornerRoundingType = newRoundingType; - __instanceLock__.unlock(); - - ASPerformBlockOnMainThread(^{ - ASDisplayNodeAssertMainThread(); - - if (oldRoundingType != newRoundingType || oldCornerRadius != newCornerRadius) { - if (oldRoundingType == ASCornerRoundingTypeDefaultSlowCALayer) { - if (newRoundingType == ASCornerRoundingTypePrecomposited) { - self.layerCornerRadius = 0.0; - if (oldCornerRadius > 0.0) { - [self displayImmediately]; - } else { - [self setNeedsDisplay]; // Async display is OK if we aren't replacing an existing .cornerRadius. - } - } - else if (newRoundingType == ASCornerRoundingTypeClipping) { - self.layerCornerRadius = 0.0; - [self _setClipCornerLayersVisible:YES]; - } else if (newRoundingType == ASCornerRoundingTypeDefaultSlowCALayer) { - self.layerCornerRadius = newCornerRadius; - } - } - else if (oldRoundingType == ASCornerRoundingTypePrecomposited) { - if (newRoundingType == ASCornerRoundingTypeDefaultSlowCALayer) { - self.layerCornerRadius = newCornerRadius; - [self setNeedsDisplay]; - } - else if (newRoundingType == ASCornerRoundingTypePrecomposited) { - // Corners are already precomposited, but the radius has changed. - // Default to async re-display. The user may force a synchronous display if desired. - [self setNeedsDisplay]; - } - else if (newRoundingType == ASCornerRoundingTypeClipping) { - [self _setClipCornerLayersVisible:YES]; - [self setNeedsDisplay]; - } - } - else if (oldRoundingType == ASCornerRoundingTypeClipping) { - if (newRoundingType == ASCornerRoundingTypeDefaultSlowCALayer) { - self.layerCornerRadius = newCornerRadius; - [self _setClipCornerLayersVisible:NO]; - } - else if (newRoundingType == ASCornerRoundingTypePrecomposited) { - [self _setClipCornerLayersVisible:NO]; - [self displayImmediately]; - } - else if (newRoundingType == ASCornerRoundingTypeClipping) { - // Clip corners already exist, but the radius has changed. - [self _updateClipCornerLayerContentsWithRadius:newCornerRadius backgroundColor:self.backgroundColor]; - } - } - } - }); -} - -- (void)recursivelySetDisplaySuspended:(BOOL)flag -{ - _recursivelySetDisplaySuspended(self, nil, flag); -} - -// TODO: Replace this with ASDisplayNodePerformBlockOnEveryNode or a variant with a condition / test block. -static void _recursivelySetDisplaySuspended(ASDisplayNode *node, CALayer *layer, BOOL flag) -{ - // If there is no layer, but node whose its view is loaded, then we can traverse down its layer hierarchy. Otherwise we must stick to the node hierarchy to avoid loading views prematurely. Note that for nodes that haven't loaded their views, they can't possibly have subviews/sublayers, so we don't need to traverse the layer hierarchy for them. - if (!layer && node && node.nodeLoaded) { - layer = node.layer; - } - - // If we don't know the node, but the layer is an async layer, get the node from the layer. - if (!node && layer && [layer isKindOfClass:[_ASDisplayLayer class]]) { - node = layer.asyncdisplaykit_node; - } - - // Set the flag on the node. If this is a pure layer (no node) then this has no effect (plain layers don't support preventing/cancelling display). - node.displaySuspended = flag; - - if (layer && !node.rasterizesSubtree) { - // If there is a layer, recurse down the layer hierarchy to set the flag on descendants. This will cover both layer-based and node-based children. - for (CALayer *sublayer in layer.sublayers) { - _recursivelySetDisplaySuspended(nil, sublayer, flag); - } - } else { - // If there is no layer (view not loaded yet) or this node rasterizes descendants (there won't be a layer tree to traverse), recurse down the subnode hierarchy to set the flag on descendants. This covers only node-based children, but for a node whose view is not loaded it can't possibly have nodeless children. - for (ASDisplayNode *subnode in node.subnodes) { - _recursivelySetDisplaySuspended(subnode, nil, flag); - } - } -} - -- (BOOL)displaySuspended -{ - MutexLocker l(__instanceLock__); - return _flags.displaySuspended; -} - -- (void)setDisplaySuspended:(BOOL)flag -{ - ASDisplayNodeAssertThreadAffinity(self); - __instanceLock__.lock(); - - // Can't do this for synchronous nodes (using layers that are not _ASDisplayLayer and so we can't control display prevention/cancel) - if (checkFlag(Synchronous) || _flags.displaySuspended == flag) { - __instanceLock__.unlock(); - return; - } - - _flags.displaySuspended = flag; - - self._locked_asyncLayer.displaySuspended = flag; - - ASDisplayNode *supernode = _supernode; - __instanceLock__.unlock(); - - if ([self _implementsDisplay]) { - // Display start and finish methods needs to happen on the main thread - ASPerformBlockOnMainThread(^{ - if (flag) { - [supernode subnodeDisplayDidFinish:self]; - } else { - [supernode subnodeDisplayWillStart:self]; - } - }); - } -} - -#pragma mark <_ASDisplayLayerDelegate> - -- (void)willDisplayAsyncLayer:(_ASDisplayLayer *)layer asynchronously:(BOOL)asynchronously -{ - // Subclass hook. -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - [self displayWillStart]; -#pragma clang diagnostic pop - - [self displayWillStartAsynchronously:asynchronously]; -} - -- (void)didDisplayAsyncLayer:(_ASDisplayLayer *)layer -{ - // Subclass hook. - [self displayDidFinish]; -} - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-implementations" -- (void)displayWillStart {} -#pragma clang diagnostic pop -- (void)displayWillStartAsynchronously:(BOOL)asynchronously -{ - ASDisplayNodeAssertMainThread(); - - ASDisplayNodeLogEvent(self, @"displayWillStart"); - // in case current node takes longer to display than it's subnodes, treat it as a dependent node - [self _pendingNodeWillDisplay:self]; - - __instanceLock__.lock(); - ASDisplayNode *supernode = _supernode; - __instanceLock__.unlock(); - - [supernode subnodeDisplayWillStart:self]; -} - -- (void)displayDidFinish -{ - ASDisplayNodeAssertMainThread(); - - ASDisplayNodeLogEvent(self, @"displayDidFinish"); - [self _pendingNodeDidDisplay:self]; - - __instanceLock__.lock(); - ASDisplayNode *supernode = _supernode; - __instanceLock__.unlock(); - - [supernode subnodeDisplayDidFinish:self]; -} - -- (void)subnodeDisplayWillStart:(ASDisplayNode *)subnode -{ - // Subclass hook - [self _pendingNodeWillDisplay:subnode]; -} - -- (void)subnodeDisplayDidFinish:(ASDisplayNode *)subnode -{ - // Subclass hook - [self _pendingNodeDidDisplay:subnode]; -} - -#pragma mark - -// We are only the delegate for the layer when we are layer-backed, as UIView performs this function normally -- (id)actionForLayer:(CALayer *)layer forKey:(NSString *)event -{ - if (event == kCAOnOrderIn) { - [self __enterHierarchy]; - } else if (event == kCAOnOrderOut) { - [self __exitHierarchy]; - } - - ASDisplayNodeAssert(_flags.layerBacked, @"We shouldn't get called back here unless we are layer-backed."); - return (id)kCFNull; -} - -#pragma mark - Error Handling - -+ (void)setNonFatalErrorBlock:(ASDisplayNodeNonFatalErrorBlock)nonFatalErrorBlock -{ - if (_nonFatalErrorBlock != nonFatalErrorBlock) { - _nonFatalErrorBlock = [nonFatalErrorBlock copy]; - } -} - -+ (ASDisplayNodeNonFatalErrorBlock)nonFatalErrorBlock -{ - return _nonFatalErrorBlock; -} - -#pragma mark - Converting to and from the Node's Coordinate System - -- (CATransform3D)_transformToAncestor:(ASDisplayNode *)ancestor -{ - CATransform3D transform = CATransform3DIdentity; - ASDisplayNode *currentNode = self; - while (currentNode.supernode) { - if (currentNode == ancestor) { - return transform; - } - - CGPoint anchorPoint = currentNode.anchorPoint; - CGRect bounds = currentNode.bounds; - CGPoint position = currentNode.position; - CGPoint origin = CGPointMake(position.x - bounds.size.width * anchorPoint.x, - position.y - bounds.size.height * anchorPoint.y); - - transform = CATransform3DTranslate(transform, origin.x, origin.y, 0); - transform = CATransform3DTranslate(transform, -bounds.origin.x, -bounds.origin.y, 0); - currentNode = currentNode.supernode; - } - return transform; -} - -static inline CATransform3D _calculateTransformFromReferenceToTarget(ASDisplayNode *referenceNode, ASDisplayNode *targetNode) -{ - ASDisplayNode *ancestor = ASDisplayNodeFindClosestCommonAncestor(referenceNode, targetNode); - - // Transform into global (away from reference coordinate space) - CATransform3D transformToGlobal = [referenceNode _transformToAncestor:ancestor]; - - // Transform into local (via inverse transform from target to ancestor) - CATransform3D transformToLocal = CATransform3DInvert([targetNode _transformToAncestor:ancestor]); - - return CATransform3DConcat(transformToGlobal, transformToLocal); -} - -- (CGPoint)convertPoint:(CGPoint)point fromNode:(ASDisplayNode *)node -{ - ASDisplayNodeAssertThreadAffinity(self); - - /** - * When passed node=nil, all methods in this family use the UIView-style - * behavior – that is, convert from/to window coordinates if there's a window, - * otherwise return the point untransformed. - */ - if (node == nil && self.nodeLoaded) { - CALayer *layer = self.layer; - if (UIWindow *window = ASFindWindowOfLayer(layer)) { - return [layer convertPoint:point fromLayer:window.layer]; - } else { - return point; - } - } - - // Get root node of the accessible node hierarchy, if node not specified - node = node ? : ASDisplayNodeUltimateParentOfNode(self); - - // Calculate transform to map points between coordinate spaces - CATransform3D nodeTransform = _calculateTransformFromReferenceToTarget(node, self); - CGAffineTransform flattenedTransform = CATransform3DGetAffineTransform(nodeTransform); - ASDisplayNodeAssertTrue(CATransform3DIsAffine(nodeTransform)); - - // Apply to point - return CGPointApplyAffineTransform(point, flattenedTransform); -} - -- (CGPoint)convertPoint:(CGPoint)point toNode:(ASDisplayNode *)node -{ - ASDisplayNodeAssertThreadAffinity(self); - - if (node == nil && self.nodeLoaded) { - CALayer *layer = self.layer; - if (UIWindow *window = ASFindWindowOfLayer(layer)) { - return [layer convertPoint:point toLayer:window.layer]; - } else { - return point; - } - } - - // Get root node of the accessible node hierarchy, if node not specified - node = node ? : ASDisplayNodeUltimateParentOfNode(self); - - // Calculate transform to map points between coordinate spaces - CATransform3D nodeTransform = _calculateTransformFromReferenceToTarget(self, node); - CGAffineTransform flattenedTransform = CATransform3DGetAffineTransform(nodeTransform); - ASDisplayNodeAssertTrue(CATransform3DIsAffine(nodeTransform)); - - // Apply to point - return CGPointApplyAffineTransform(point, flattenedTransform); -} - -- (CGRect)convertRect:(CGRect)rect fromNode:(ASDisplayNode *)node -{ - ASDisplayNodeAssertThreadAffinity(self); - - if (node == nil && self.nodeLoaded) { - CALayer *layer = self.layer; - if (UIWindow *window = ASFindWindowOfLayer(layer)) { - return [layer convertRect:rect fromLayer:window.layer]; - } else { - return rect; - } - } - - // Get root node of the accessible node hierarchy, if node not specified - node = node ? : ASDisplayNodeUltimateParentOfNode(self); - - // Calculate transform to map points between coordinate spaces - CATransform3D nodeTransform = _calculateTransformFromReferenceToTarget(node, self); - CGAffineTransform flattenedTransform = CATransform3DGetAffineTransform(nodeTransform); - ASDisplayNodeAssertTrue(CATransform3DIsAffine(nodeTransform)); - - // Apply to rect - return CGRectApplyAffineTransform(rect, flattenedTransform); -} - -- (CGRect)convertRect:(CGRect)rect toNode:(ASDisplayNode *)node -{ - ASDisplayNodeAssertThreadAffinity(self); - - if (node == nil && self.nodeLoaded) { - CALayer *layer = self.layer; - if (UIWindow *window = ASFindWindowOfLayer(layer)) { - return [layer convertRect:rect toLayer:window.layer]; - } else { - return rect; - } - } - - // Get root node of the accessible node hierarchy, if node not specified - node = node ? : ASDisplayNodeUltimateParentOfNode(self); - - // Calculate transform to map points between coordinate spaces - CATransform3D nodeTransform = _calculateTransformFromReferenceToTarget(self, node); - CGAffineTransform flattenedTransform = CATransform3DGetAffineTransform(nodeTransform); - ASDisplayNodeAssertTrue(CATransform3DIsAffine(nodeTransform)); - - // Apply to rect - return CGRectApplyAffineTransform(rect, flattenedTransform); -} - -#pragma mark - Managing the Node Hierarchy - -ASDISPLAYNODE_INLINE bool shouldDisableNotificationsForMovingBetweenParents(ASDisplayNode *from, ASDisplayNode *to) { - if (!from || !to) return NO; - if (from.isSynchronous) return NO; - if (to.isSynchronous) return NO; - if (from.isInHierarchy != to.isInHierarchy) return NO; - return YES; -} - -/// Returns incremented value of i if i is not NSNotFound -ASDISPLAYNODE_INLINE NSInteger incrementIfFound(NSInteger i) { - return i == NSNotFound ? NSNotFound : i + 1; -} - -/// Returns if a node is a member of a rasterized tree -ASDISPLAYNODE_INLINE BOOL canUseViewAPI(ASDisplayNode *node, ASDisplayNode *subnode) { - return (subnode.isLayerBacked == NO && node.isLayerBacked == NO); -} - -/// Returns if node is a member of a rasterized tree -ASDISPLAYNODE_INLINE BOOL subtreeIsRasterized(ASDisplayNode *node) { - return (node.rasterizesSubtree || (node.hierarchyState & ASHierarchyStateRasterized)); -} - -// NOTE: This method must be dealloc-safe (should not retain self). -- (ASDisplayNode *)supernode -{ - MutexLocker l(__instanceLock__); - return _supernode; -} - -- (void)_setSupernode:(ASDisplayNode *)newSupernode -{ - BOOL supernodeDidChange = NO; - ASDisplayNode *oldSupernode = nil; - { - MutexLocker l(__instanceLock__); - if (_supernode != newSupernode) { - oldSupernode = _supernode; // Access supernode properties outside of lock to avoid remote chance of deadlock, - // in case supernode implementation must access one of our properties. - _supernode = newSupernode; - supernodeDidChange = YES; - } - } - - if (supernodeDidChange) { - ASDisplayNodeLogEvent(self, @"supernodeDidChange: %@, oldValue = %@", ASObjectDescriptionMakeTiny(newSupernode), ASObjectDescriptionMakeTiny(oldSupernode)); - // Hierarchy state - ASHierarchyState stateToEnterOrExit = (newSupernode ? newSupernode.hierarchyState - : oldSupernode.hierarchyState); - - // Rasterized state - BOOL parentWasOrIsRasterized = (newSupernode ? newSupernode.rasterizesSubtree - : oldSupernode.rasterizesSubtree); - if (parentWasOrIsRasterized) { - stateToEnterOrExit |= ASHierarchyStateRasterized; - } - if (newSupernode) { - - // Now that we have a supernode, propagate its traits to self. - // This should be done before possibly forcing self to load so we have traits in -didLoad - ASTraitCollectionPropagateDown(self, newSupernode.primitiveTraitCollection); - - if (!parentWasOrIsRasterized && newSupernode.nodeLoaded) { - // Trigger the subnode to load its layer, which will create its view if it needs one. - // By doing this prior to the downward propagation of newSupernode's interface state, - // we can guarantee that -didEnterVisibleState is only called with .isNodeLoaded = YES. - [self layer]; - } - - [self enterHierarchyState:stateToEnterOrExit]; - - // If a node was added to a supernode, the supernode could be in a layout pending state. All of the hierarchy state - // properties related to the transition need to be copied over as well as propagated down the subtree. - // This is especially important as with automatic subnode management, adding subnodes can happen while a transition - // is in fly - if (ASHierarchyStateIncludesLayoutPending(stateToEnterOrExit)) { - int32_t pendingTransitionId = newSupernode->_pendingTransitionID; - if (pendingTransitionId != ASLayoutElementContextInvalidTransitionID) { - { - _pendingTransitionID = pendingTransitionId; - - // Propagate down the new pending transition id - ASDisplayNodePerformBlockOnEverySubnode(self, NO, ^(ASDisplayNode * _Nonnull node) { - node->_pendingTransitionID = pendingTransitionId; - }); - } - } - } - } else { - // If a node will be removed from the supernode it should go out from the layout pending state to remove all - // layout pending state related properties on the node - stateToEnterOrExit |= ASHierarchyStateLayoutPending; - - [self exitHierarchyState:stateToEnterOrExit]; - - // We only need to explicitly exit hierarchy here if we were rasterized. - // Otherwise we will exit the hierarchy when our view/layer does so - // which has some nice carry-over machinery to handle cases where we are removed from a hierarchy - // and then added into it again shortly after. - __instanceLock__.lock(); - BOOL isInHierarchy = _flags.isInHierarchy; - __instanceLock__.unlock(); - - if (parentWasOrIsRasterized && isInHierarchy) { - [self __exitHierarchy]; - } - } - } -} - -- (NSArray *)subnodes -{ - MutexLocker l(__instanceLock__); - if (_cachedSubnodes == nil) { - _cachedSubnodes = [_subnodes copy]; - } else { - ASDisplayNodeAssert(ASObjectIsEqual(_cachedSubnodes, _subnodes), @"Expected _subnodes and _cachedSubnodes to have the same contents."); - } - return _cachedSubnodes ?: @[]; -} - -/* - * Central private helper method that should eventually be called if submethods add, insert or replace subnodes - * This method is called with thread affinity and without lock held. - * - * @param subnode The subnode to insert - * @param subnodeIndex The index in _subnodes to insert it - * @param viewSublayerIndex The index in layer.sublayers (not view.subviews) at which to insert the view (use if we can use the view API) otherwise pass NSNotFound - * @param sublayerIndex The index in layer.sublayers at which to insert the layer (use if either parent or subnode is layer-backed) otherwise pass NSNotFound - * @param oldSubnode Remove this subnode before inserting; ok to be nil if no removal is desired - */ -- (void)_insertSubnode:(ASDisplayNode *)subnode atSubnodeIndex:(NSInteger)subnodeIndex sublayerIndex:(NSInteger)sublayerIndex andRemoveSubnode:(ASDisplayNode *)oldSubnode -{ - ASDisplayNodeAssertThreadAffinity(self); - // TODO: Disabled due to PR: https://github.com/TextureGroup/Texture/pull/1204 - // ASAssertUnlocked(__instanceLock__); - - if (subnode == nil || subnode == self) { - ASDisplayNodeFailAssert(@"Cannot insert a nil subnode or self as subnode"); - return; - } - - if (subnodeIndex == NSNotFound) { - ASDisplayNodeFailAssert(@"Try to insert node on an index that was not found"); - return; - } - - if (self.layerBacked && !subnode.layerBacked) { - ASDisplayNodeFailAssert(@"Cannot add a view-backed node as a subnode of a layer-backed node. Supernode: %@, subnode: %@", self, subnode); - return; - } - - BOOL isRasterized = subtreeIsRasterized(self); - if (isRasterized && subnode.nodeLoaded) { - ASDisplayNodeFailAssert(@"Cannot add loaded node %@ to rasterized subtree of node %@", ASObjectDescriptionMakeTiny(subnode), ASObjectDescriptionMakeTiny(self)); - return; - } - - __instanceLock__.lock(); - NSUInteger subnodesCount = _subnodes.count; - __instanceLock__.unlock(); - if (subnodeIndex > subnodesCount || subnodeIndex < 0) { - ASDisplayNodeFailAssert(@"Cannot insert a subnode at index %ld. Count is %ld", (long)subnodeIndex, (long)subnodesCount); - return; - } - - // Disable appearance methods during move between supernodes, but make sure we restore their state after we do our thing - ASDisplayNode *oldParent = subnode.supernode; - BOOL disableNotifications = shouldDisableNotificationsForMovingBetweenParents(oldParent, self); - if (disableNotifications) { - [subnode __incrementVisibilityNotificationsDisabled]; - } - - [subnode _removeFromSupernode]; - [oldSubnode _removeFromSupernode]; - - __instanceLock__.lock(); - if (_subnodes == nil) { - _subnodes = [[NSMutableArray alloc] init]; - } - [_subnodes insertObject:subnode atIndex:subnodeIndex]; - _cachedSubnodes = nil; - __instanceLock__.unlock(); - - // This call will apply our .hierarchyState to the new subnode. - // If we are a managed hierarchy, as in ASCellNode trees, it will also apply our .interfaceState. - [subnode _setSupernode:self]; - - // If this subnode will be rasterized, enter hierarchy if needed - // TODO: Move this into _setSupernode: ? - if (isRasterized) { - if (self.inHierarchy) { - [subnode __enterHierarchy]; - } - } else if (self.nodeLoaded) { - // If not rasterizing, and node is loaded insert the subview/sublayer now. - [self _insertSubnodeSubviewOrSublayer:subnode atIndex:sublayerIndex]; - } // Otherwise we will insert subview/sublayer when we get loaded - - ASDisplayNodeAssert(disableNotifications == shouldDisableNotificationsForMovingBetweenParents(oldParent, self), @"Invariant violated"); - if (disableNotifications) { - [subnode __decrementVisibilityNotificationsDisabled]; - } -} - -/* - * Inserts the view or layer of the given node at the given index - * - * @param subnode The subnode to insert - * @param idx The index in _view.subviews or _layer.sublayers at which to insert the subnode.view or - * subnode.layer of the subnode - */ -- (void)_insertSubnodeSubviewOrSublayer:(ASDisplayNode *)subnode atIndex:(NSInteger)idx -{ - ASDisplayNodeAssertMainThread(); - ASDisplayNodeAssert(self.nodeLoaded, @"_insertSubnodeSubviewOrSublayer:atIndex: should never be called before our own view is created"); - - ASDisplayNodeAssert(idx != NSNotFound, @"Try to insert node on an index that was not found"); - if (idx == NSNotFound) { - return; - } - - // Because the view and layer can only be created and destroyed on Main, that is also the only thread - // where the view and layer can change. We can avoid locking. - - // If we can use view API, do. Due to an apple bug, -insertSubview:atIndex: actually wants a LAYER index, - // which we pass in. - if (canUseViewAPI(self, subnode)) { - [_view insertSubview:subnode.view atIndex:idx]; - } else { - [_layer insertSublayer:subnode.layer atIndex:(unsigned int)idx]; - } -} - -- (void)addSubnode:(ASDisplayNode *)subnode -{ - ASDisplayNodeLogEvent(self, @"addSubnode: %@ with automaticallyManagesSubnodes: %@", - subnode, self.automaticallyManagesSubnodes ? @"YES" : @"NO"); - [self _addSubnode:subnode]; -} - -- (void)_addSubnode:(ASDisplayNode *)subnode -{ - ASDisplayNodeAssertThreadAffinity(self); - - ASDisplayNodeAssert(subnode, @"Cannot insert a nil subnode"); - - // Don't add if it's already a subnode - ASDisplayNode *oldParent = subnode.supernode; - if (!subnode || subnode == self || oldParent == self) { - return; - } - - NSUInteger subnodesIndex; - NSUInteger sublayersIndex; - { - MutexLocker l(__instanceLock__); - subnodesIndex = _subnodes.count; - sublayersIndex = _layer.sublayers.count; - } - - [self _insertSubnode:subnode atSubnodeIndex:subnodesIndex sublayerIndex:sublayersIndex andRemoveSubnode:nil]; -} - -- (void)_addSubnodeViewsAndLayers -{ - ASDisplayNodeAssertMainThread(); - - TIME_SCOPED(_debugTimeToAddSubnodeViews); - - for (ASDisplayNode *node in self.subnodes) { - [self _addSubnodeSubviewOrSublayer:node]; - } -} - -- (void)_addSubnodeSubviewOrSublayer:(ASDisplayNode *)subnode -{ - ASDisplayNodeAssertMainThread(); - - // Due to a bug in Apple's framework we have to use the layer index to insert a subview - // so just use the count of the sublayers to add the subnode - NSInteger idx = _layer.sublayers.count; // No locking is needed as it's main thread only - [self _insertSubnodeSubviewOrSublayer:subnode atIndex:idx]; -} - -- (void)replaceSubnode:(ASDisplayNode *)oldSubnode withSubnode:(ASDisplayNode *)replacementSubnode -{ - ASDisplayNodeLogEvent(self, @"replaceSubnode: %@ withSubnode: %@ with automaticallyManagesSubnodes: %@", - oldSubnode, replacementSubnode, self.automaticallyManagesSubnodes ? @"YES" : @"NO"); - [self _replaceSubnode:oldSubnode withSubnode:replacementSubnode]; -} - -- (void)_replaceSubnode:(ASDisplayNode *)oldSubnode withSubnode:(ASDisplayNode *)replacementSubnode -{ - ASDisplayNodeAssertThreadAffinity(self); - - if (replacementSubnode == nil) { - ASDisplayNodeFailAssert(@"Invalid subnode to replace"); - return; - } - - if (oldSubnode.supernode != self) { - ASDisplayNodeFailAssert(@"Old Subnode to replace must be a subnode"); - return; - } - - ASDisplayNodeAssert(!(self.nodeLoaded && !oldSubnode.nodeLoaded), @"We have view loaded, but child node does not."); - - NSInteger subnodeIndex; - NSInteger sublayerIndex = NSNotFound; - { - MutexLocker l(__instanceLock__); - ASDisplayNodeAssert(_subnodes, @"You should have subnodes if you have a subnode"); - - subnodeIndex = [_subnodes indexOfObjectIdenticalTo:oldSubnode]; - - // Don't bother figuring out the sublayerIndex if in a rasterized subtree, because there are no layers in the - // hierarchy and none of this could possibly work. - if (subtreeIsRasterized(self) == NO) { - if (_layer) { - sublayerIndex = [_layer.sublayers indexOfObjectIdenticalTo:oldSubnode.layer]; - ASDisplayNodeAssert(sublayerIndex != NSNotFound, @"Somehow oldSubnode's supernode is self, yet we could not find it in our layers to replace"); - if (sublayerIndex == NSNotFound) { - return; - } - } - } - } - - [self _insertSubnode:replacementSubnode atSubnodeIndex:subnodeIndex sublayerIndex:sublayerIndex andRemoveSubnode:oldSubnode]; -} - -- (void)insertSubnode:(ASDisplayNode *)subnode belowSubnode:(ASDisplayNode *)below -{ - ASDisplayNodeLogEvent(self, @"insertSubnode: %@ belowSubnode: %@ with automaticallyManagesSubnodes: %@", - subnode, below, self.automaticallyManagesSubnodes ? @"YES" : @"NO"); - [self _insertSubnode:subnode belowSubnode:below]; -} - -- (void)_insertSubnode:(ASDisplayNode *)subnode belowSubnode:(ASDisplayNode *)below -{ - ASDisplayNodeAssertThreadAffinity(self); - // TODO: Disabled due to PR: https://github.com/TextureGroup/Texture/pull/1204 - // ASAssertUnlocked(__instanceLock__); - - if (subnode == nil) { - ASDisplayNodeFailAssert(@"Cannot insert a nil subnode"); - return; - } - - if (below.supernode != self) { - ASDisplayNodeFailAssert(@"Node to insert below must be a subnode"); - return; - } - - NSInteger belowSubnodeIndex; - NSInteger belowSublayerIndex = NSNotFound; - { - MutexLocker l(__instanceLock__); - ASDisplayNodeAssert(_subnodes, @"You should have subnodes if you have a subnode"); - - belowSubnodeIndex = [_subnodes indexOfObjectIdenticalTo:below]; - - // Don't bother figuring out the sublayerIndex if in a rasterized subtree, because there are no layers in the - // hierarchy and none of this could possibly work. - if (subtreeIsRasterized(self) == NO) { - if (_layer) { - belowSublayerIndex = [_layer.sublayers indexOfObjectIdenticalTo:below.layer]; - ASDisplayNodeAssert(belowSublayerIndex != NSNotFound, @"Somehow below's supernode is self, yet we could not find it in our layers to reference"); - if (belowSublayerIndex == NSNotFound) - return; - } - - ASDisplayNodeAssert(belowSubnodeIndex != NSNotFound, @"Couldn't find above in subnodes"); - - // If the subnode is already in the subnodes array / sublayers and it's before the below node, removing it to - // insert it will mess up our calculation - if (subnode.supernode == self) { - NSInteger currentIndexInSubnodes = [_subnodes indexOfObjectIdenticalTo:subnode]; - if (currentIndexInSubnodes < belowSubnodeIndex) { - belowSubnodeIndex--; - } - if (_layer) { - NSInteger currentIndexInSublayers = [_layer.sublayers indexOfObjectIdenticalTo:subnode.layer]; - if (currentIndexInSublayers < belowSublayerIndex) { - belowSublayerIndex--; - } - } - } - } - } - - ASDisplayNodeAssert(belowSubnodeIndex != NSNotFound, @"Couldn't find below in subnodes"); - - [self _insertSubnode:subnode atSubnodeIndex:belowSubnodeIndex sublayerIndex:belowSublayerIndex andRemoveSubnode:nil]; -} - -- (void)insertSubnode:(ASDisplayNode *)subnode aboveSubnode:(ASDisplayNode *)above -{ - ASDisplayNodeLogEvent(self, @"insertSubnode: %@ abodeSubnode: %@ with automaticallyManagesSubnodes: %@", - subnode, above, self.automaticallyManagesSubnodes ? @"YES" : @"NO"); - [self _insertSubnode:subnode aboveSubnode:above]; -} - -- (void)_insertSubnode:(ASDisplayNode *)subnode aboveSubnode:(ASDisplayNode *)above -{ - ASDisplayNodeAssertThreadAffinity(self); - // TODO: Disabled due to PR: https://github.com/TextureGroup/Texture/pull/1204 - // ASAssertUnlocked(__instanceLock__); - - if (subnode == nil) { - ASDisplayNodeFailAssert(@"Cannot insert a nil subnode"); - return; - } - - if (above.supernode != self) { - ASDisplayNodeFailAssert(@"Node to insert above must be a subnode"); - return; - } - - NSInteger aboveSubnodeIndex; - NSInteger aboveSublayerIndex = NSNotFound; - { - MutexLocker l(__instanceLock__); - ASDisplayNodeAssert(_subnodes, @"You should have subnodes if you have a subnode"); - - aboveSubnodeIndex = [_subnodes indexOfObjectIdenticalTo:above]; - - // Don't bother figuring out the sublayerIndex if in a rasterized subtree, because there are no layers in the - // hierarchy and none of this could possibly work. - if (subtreeIsRasterized(self) == NO) { - if (_layer) { - aboveSublayerIndex = [_layer.sublayers indexOfObjectIdenticalTo:above.layer]; - ASDisplayNodeAssert(aboveSublayerIndex != NSNotFound, @"Somehow above's supernode is self, yet we could not find it in our layers to replace"); - if (aboveSublayerIndex == NSNotFound) - return; - } - - ASDisplayNodeAssert(aboveSubnodeIndex != NSNotFound, @"Couldn't find above in subnodes"); - - // If the subnode is already in the subnodes array / sublayers and it's before the below node, removing it to - // insert it will mess up our calculation - if (subnode.supernode == self) { - NSInteger currentIndexInSubnodes = [_subnodes indexOfObjectIdenticalTo:subnode]; - if (currentIndexInSubnodes <= aboveSubnodeIndex) { - aboveSubnodeIndex--; - } - if (_layer) { - NSInteger currentIndexInSublayers = [_layer.sublayers indexOfObjectIdenticalTo:subnode.layer]; - if (currentIndexInSublayers <= aboveSublayerIndex) { - aboveSublayerIndex--; - } - } - } - } - } - - [self _insertSubnode:subnode atSubnodeIndex:incrementIfFound(aboveSubnodeIndex) sublayerIndex:incrementIfFound(aboveSublayerIndex) andRemoveSubnode:nil]; -} - -- (void)insertSubnode:(ASDisplayNode *)subnode atIndex:(NSInteger)idx -{ - ASDisplayNodeLogEvent(self, @"insertSubnode: %@ atIndex: %td with automaticallyManagesSubnodes: %@", - subnode, idx, self.automaticallyManagesSubnodes ? @"YES" : @"NO"); - [self _insertSubnode:subnode atIndex:idx]; -} - -- (void)_insertSubnode:(ASDisplayNode *)subnode atIndex:(NSInteger)idx -{ - ASDisplayNodeAssertThreadAffinity(self); - // TODO: Disabled due to PR: https://github.com/TextureGroup/Texture/pull/1204 - // ASAssertUnlocked(__instanceLock__); - - if (subnode == nil) { - ASDisplayNodeFailAssert(@"Cannot insert a nil subnode"); - return; - } - - NSInteger sublayerIndex = NSNotFound; - { - MutexLocker l(__instanceLock__); - - if (idx > _subnodes.count || idx < 0) { - ASDisplayNodeFailAssert(@"Cannot insert a subnode at index %ld. Count is %ld", (long)idx, (long)_subnodes.count); - return; - } - - // Don't bother figuring out the sublayerIndex if in a rasterized subtree, because there are no layers in the - // hierarchy and none of this could possibly work. - if (subtreeIsRasterized(self) == NO) { - // Account for potentially having other subviews - if (_layer && idx == 0) { - sublayerIndex = 0; - } else if (_layer) { - ASDisplayNode *positionInRelationTo = (_subnodes.count > 0 && idx > 0) ? _subnodes[idx - 1] : nil; - if (positionInRelationTo) { - sublayerIndex = incrementIfFound([_layer.sublayers indexOfObjectIdenticalTo:positionInRelationTo.layer]); - } - } - } - } - - [self _insertSubnode:subnode atSubnodeIndex:idx sublayerIndex:sublayerIndex andRemoveSubnode:nil]; -} - -- (void)_removeSubnode:(ASDisplayNode *)subnode -{ - ASDisplayNodeAssertThreadAffinity(self); - // TODO: Disabled due to PR: https://github.com/TextureGroup/Texture/pull/1204 - // ASAssertUnlocked(__instanceLock__); - - // Don't call self.supernode here because that will retain/autorelease the supernode. This method -_removeSupernode: is often called while tearing down a node hierarchy, and the supernode in question might be in the middle of its -dealloc. The supernode is never messaged, only compared by value, so this is safe. - // The particular issue that triggers this edge case is when a node calls -removeFromSupernode on a subnode from within its own -dealloc method. - if (!subnode || subnode.supernode != self) { - return; - } - - __instanceLock__.lock(); - [_subnodes removeObjectIdenticalTo:subnode]; - _cachedSubnodes = nil; - __instanceLock__.unlock(); - - [subnode _setSupernode:nil]; -} - -- (void)removeFromSupernode -{ - ASDisplayNodeLogEvent(self, @"removeFromSupernode with automaticallyManagesSubnodes: %@", - self.automaticallyManagesSubnodes ? @"YES" : @"NO"); - [self _removeFromSupernode]; -} - -- (void)_removeFromSupernode -{ - ASDisplayNodeAssertThreadAffinity(self); - // TODO: Disabled due to PR: https://github.com/TextureGroup/Texture/pull/1204 - // ASAssertUnlocked(__instanceLock__); - - __instanceLock__.lock(); - __weak ASDisplayNode *supernode = _supernode; - __weak UIView *view = _view; - __weak CALayer *layer = _layer; - __instanceLock__.unlock(); - - [self _removeFromSupernode:supernode view:view layer:layer]; -} - -- (void)_removeFromSupernodeIfEqualTo:(ASDisplayNode *)supernode -{ - ASDisplayNodeAssertThreadAffinity(self); - // TODO: Disabled due to PR: https://github.com/TextureGroup/Texture/pull/1204 - // ASAssertUnlocked(__instanceLock__); - - __instanceLock__.lock(); - - // Only remove if supernode is still the expected supernode - if (!ASObjectIsEqual(_supernode, supernode)) { - __instanceLock__.unlock(); - return; - } - - __weak UIView *view = _view; - __weak CALayer *layer = _layer; - __instanceLock__.unlock(); - - [self _removeFromSupernode:supernode view:view layer:layer]; -} - -- (void)_removeFromSupernode:(ASDisplayNode *)supernode view:(UIView *)view layer:(CALayer *)layer -{ - // Note: we continue even if supernode is nil to ensure view/layer are removed from hierarchy. - - if (supernode != nil) { - } - - // Clear supernode's reference to us before removing the view from the hierarchy, as _ASDisplayView - // will trigger us to clear our _supernode pointer in willMoveToSuperview:nil. - // This may result in removing the last strong reference, triggering deallocation after this method. - [supernode _removeSubnode:self]; - - if (view != nil) { - [view removeFromSuperview]; - } else if (layer != nil) { - [layer removeFromSuperlayer]; - } -} - -#pragma mark - Visibility API - -- (BOOL)__visibilityNotificationsDisabled -{ - // Currently, this method is only used by the testing infrastructure to verify this internal feature. - MutexLocker l(__instanceLock__); - return _flags.visibilityNotificationsDisabled > 0; -} - -- (BOOL)__selfOrParentHasVisibilityNotificationsDisabled -{ - MutexLocker l(__instanceLock__); - return (_hierarchyState & ASHierarchyStateTransitioningSupernodes); -} - -- (void)__incrementVisibilityNotificationsDisabled -{ - __instanceLock__.lock(); - const size_t maxVisibilityIncrement = (1ULL< 0, @"Can't decrement past 0"); - if (_flags.visibilityNotificationsDisabled > 0) { - _flags.visibilityNotificationsDisabled--; - } - BOOL visibilityNotificationsDisabled = (_flags.visibilityNotificationsDisabled == 0); - __instanceLock__.unlock(); - - if (visibilityNotificationsDisabled) { - // Must have just transitioned from 1 to 0. Notify all subnodes that we are no longer in a disabled state. - // FIXME: This system should be revisited when refactoring and consolidating the implementation of the - // addSubnode: and insertSubnode:... methods. As implemented, though logically irrelevant for expected use cases, - // multiple nodes in the subtree below may have a non-zero visibilityNotification count and still have - // the ASHierarchyState bit cleared (the only value checked when reading this state). - [self exitHierarchyState:ASHierarchyStateTransitioningSupernodes]; - } -} - -#pragma mark - Placeholder - -- (void)_locked_layoutPlaceholderIfNecessary -{ - ASAssertLocked(__instanceLock__); - if ([self _locked_shouldHavePlaceholderLayer]) { - [self _locked_setupPlaceholderLayerIfNeeded]; - } - // Update the placeholderLayer size in case the node size has changed since the placeholder was added. - _placeholderLayer.frame = self.threadSafeBounds; -} - -- (BOOL)_locked_shouldHavePlaceholderLayer -{ - ASAssertLocked(__instanceLock__); - return (_placeholderEnabled && [self _implementsDisplay]); -} - -- (void)_locked_setupPlaceholderLayerIfNeeded -{ - ASDisplayNodeAssertMainThread(); - ASAssertLocked(__instanceLock__); - - if (!_placeholderLayer) { - _placeholderLayer = [CALayer layer]; - // do not set to CGFLOAT_MAX in the case that something needs to be overtop the placeholder - _placeholderLayer.zPosition = 9999.0; - } - - if (_placeholderLayer.contents == nil) { - if (!_placeholderImage) { - _placeholderImage = [self placeholderImage]; - } - if (_placeholderImage) { - BOOL stretchable = !UIEdgeInsetsEqualToEdgeInsets(_placeholderImage.capInsets, UIEdgeInsetsZero); - if (stretchable) { - ASDisplayNodeSetResizableContents(_placeholderLayer, _placeholderImage); - } else { - _placeholderLayer.contentsScale = self.contentsScale; - _placeholderLayer.contents = (id)_placeholderImage.CGImage; - } - } - } -} - -- (UIImage *)placeholderImage -{ - // Subclass hook - return nil; -} - -- (BOOL)placeholderShouldPersist -{ - // Subclass hook - return NO; -} - -#pragma mark - Hierarchy State - -- (BOOL)isInHierarchy -{ - MutexLocker l(__instanceLock__); - return _flags.isInHierarchy; -} - -- (void)__enterHierarchy -{ - ASDisplayNodeAssertMainThread(); - ASDisplayNodeAssert(!_flags.isEnteringHierarchy, @"Should not cause recursive __enterHierarchy"); - ASDisplayNodeLogEvent(self, @"enterHierarchy"); - - // Profiling has shown that locking this method is beneficial, so each of the property accesses don't have to lock and unlock. - __instanceLock__.lock(); - - if (!_flags.isInHierarchy && !_flags.visibilityNotificationsDisabled && ![self __selfOrParentHasVisibilityNotificationsDisabled]) { - _flags.isEnteringHierarchy = YES; - _flags.isInHierarchy = YES; - - // Don't call -willEnterHierarchy while holding __instanceLock__. - // This method and subsequent ones (i.e -interfaceState and didEnter(.*)State) - // don't expect that they are called while the lock is being held. - // More importantly, didEnter(.*)State methods are meant to be overriden by clients. - // And so they can potentially walk up the node tree and cause deadlocks, or do expensive tasks and cause the lock to be held for too long. - __instanceLock__.unlock(); - [self willEnterHierarchy]; - for (ASDisplayNode *subnode in self.subnodes) { - [subnode __enterHierarchy]; - } - __instanceLock__.lock(); - - _flags.isEnteringHierarchy = NO; - - // If we don't have contents finished drawing by the time we are on screen, immediately add the placeholder (if it is enabled and we do have something to draw). - if (self.contents == nil && [self _implementsDisplay]) { - CALayer *layer = self.layer; - [layer setNeedsDisplay]; - - if ([self _locked_shouldHavePlaceholderLayer]) { - [CATransaction begin]; - [CATransaction setDisableActions:YES]; - [self _locked_setupPlaceholderLayerIfNeeded]; - _placeholderLayer.opacity = 1.0; - [CATransaction commit]; - [layer addSublayer:_placeholderLayer]; - } - } - } - - __instanceLock__.unlock(); - - [self didEnterHierarchy]; -} - -- (void)__exitHierarchy -{ - ASDisplayNodeAssertMainThread(); - ASDisplayNodeAssert(!_flags.isExitingHierarchy, @"Should not cause recursive __exitHierarchy"); - ASDisplayNodeLogEvent(self, @"exitHierarchy"); - - // Profiling has shown that locking this method is beneficial, so each of the property accesses don't have to lock and unlock. - __instanceLock__.lock(); - - if (_flags.isInHierarchy && !_flags.visibilityNotificationsDisabled && ![self __selfOrParentHasVisibilityNotificationsDisabled]) { - _flags.isExitingHierarchy = YES; - _flags.isInHierarchy = NO; - - // Don't call -didExitHierarchy while holding __instanceLock__. - // This method and subsequent ones (i.e -interfaceState and didExit(.*)State) - // don't expect that they are called while the lock is being held. - // More importantly, didExit(.*)State methods are meant to be overriden by clients. - // And so they can potentially walk up the node tree and cause deadlocks, or do expensive tasks and cause the lock to be held for too long. - __instanceLock__.unlock(); - [self didExitHierarchy]; - for (ASDisplayNode *subnode in self.subnodes) { - [subnode __exitHierarchy]; - } - __instanceLock__.lock(); - - _flags.isExitingHierarchy = NO; - } - - __instanceLock__.unlock(); -} - -- (void)enterHierarchyState:(ASHierarchyState)hierarchyState -{ - if (hierarchyState == ASHierarchyStateNormal) { - return; // This method is a no-op with a 0-bitfield argument, so don't bother recursing. - } - - ASDisplayNodePerformBlockOnEveryNode(nil, self, NO, ^(ASDisplayNode *node) { - node.hierarchyState |= hierarchyState; - }); -} - -- (void)exitHierarchyState:(ASHierarchyState)hierarchyState -{ - if (hierarchyState == ASHierarchyStateNormal) { - return; // This method is a no-op with a 0-bitfield argument, so don't bother recursing. - } - ASDisplayNodePerformBlockOnEveryNode(nil, self, NO, ^(ASDisplayNode *node) { - node.hierarchyState &= (~hierarchyState); - }); -} - -- (ASHierarchyState)hierarchyState -{ - MutexLocker l(__instanceLock__); - return _hierarchyState; -} - -- (void)setHierarchyState:(ASHierarchyState)newState -{ - ASHierarchyState oldState = ASHierarchyStateNormal; - { - MutexLocker l(__instanceLock__); - if (_hierarchyState == newState) { - return; - } - oldState = _hierarchyState; - _hierarchyState = newState; - } - - // Entered rasterization state. - if (newState & ASHierarchyStateRasterized) { - ASDisplayNodeAssert(checkFlag(Synchronous) == NO, @"Node created using -initWithViewBlock:/-initWithLayerBlock: cannot be added to subtree of node with subtree rasterization enabled. Node: %@", self); - } - - // Entered or exited range managed state. - if ((newState & ASHierarchyStateRangeManaged) != (oldState & ASHierarchyStateRangeManaged)) { - if (newState & ASHierarchyStateRangeManaged) { - [self enterInterfaceState:self.supernode.pendingInterfaceState]; - } else { - // The case of exiting a range-managed state should be fairly rare. Adding or removing the node - // to a view hierarchy will cause its interfaceState to be either fully set or unset (all fields), - // but because we might be about to be added to a view hierarchy, exiting the interface state now - // would cause inefficient churn. The tradeoff is that we may not clear contents / fetched data - // for nodes that are removed from a managed state and then retained but not used (bad idea anyway!) - } - } - - if ((newState & ASHierarchyStateLayoutPending) != (oldState & ASHierarchyStateLayoutPending)) { - if (newState & ASHierarchyStateLayoutPending) { - // Entering layout pending state - } else { - // Leaving layout pending state, reset related properties - MutexLocker l(__instanceLock__); - _pendingTransitionID = ASLayoutElementContextInvalidTransitionID; - _pendingLayoutTransition = nil; - } - } - - ASDisplayNodeLogEvent(self, @"setHierarchyState: %@", NSStringFromASHierarchyStateChange(oldState, newState)); -} - -- (void)willEnterHierarchy -{ - ASDisplayNodeAssertMainThread(); - ASDisplayNodeAssert(_flags.isEnteringHierarchy, @"You should never call -willEnterHierarchy directly. Appearance is automatically managed by ASDisplayNode"); - ASDisplayNodeAssert(!_flags.isExitingHierarchy, @"ASDisplayNode inconsistency. __enterHierarchy and __exitHierarchy are mutually exclusive"); - ASAssertUnlocked(__instanceLock__); - - if (![self supportsRangeManagedInterfaceState]) { - self.interfaceState = ASInterfaceStateInHierarchy; - } else if (ASCATransactionQueueGet().enabled) { - __instanceLock__.lock(); - ASInterfaceState state = _preExitingInterfaceState; - _preExitingInterfaceState = ASInterfaceStateNone; - __instanceLock__.unlock(); - // Layer thrash happened, revert to before exiting. - if (state != ASInterfaceStateNone) { - self.interfaceState = state; - } - } -} - -- (void)didEnterHierarchy { - ASDisplayNodeAssertMainThread(); - ASDisplayNodeAssert(!_flags.isEnteringHierarchy, @"You should never call -didEnterHierarchy directly. Appearance is automatically managed by ASDisplayNode"); - ASDisplayNodeAssert(!_flags.isExitingHierarchy, @"ASDisplayNode inconsistency. __enterHierarchy and __exitHierarchy are mutually exclusive"); - ASDisplayNodeAssert(_flags.isInHierarchy, @"ASDisplayNode inconsistency. __enterHierarchy and __exitHierarchy are mutually exclusive"); - ASAssertUnlocked(__instanceLock__); -} - -- (void)didExitHierarchy -{ - ASDisplayNodeAssertMainThread(); - ASDisplayNodeAssert(_flags.isExitingHierarchy, @"You should never call -didExitHierarchy directly. Appearance is automatically managed by ASDisplayNode"); - ASDisplayNodeAssert(!_flags.isEnteringHierarchy, @"ASDisplayNode inconsistency. __enterHierarchy and __exitHierarchy are mutually exclusive"); - ASAssertUnlocked(__instanceLock__); - - // This case is important when tearing down hierarchies. We must deliver a visibileStateDidChange:NO callback, as part our API guarantee that this method can be used for - // things like data analytics about user content viewing. We cannot call the method in the dealloc as any incidental retain operations in client code would fail. - // Additionally, it may be that a Standard UIView which is containing us is moving between hierarchies, and we should not send the call if we will be re-added in the - // same runloop. Strategy: strong reference (might be the last!), wait one runloop, and confirm we are still outside the hierarchy (both layer-backed and view-backed). - // TODO: This approach could be optimized by only performing the dispatch for root elements + recursively apply the interface state change. This would require a closer - // integration with _ASDisplayLayer to ensure that the superlayer pointer has been cleared by this stage (to check if we are root or not), or a different delegate call. - -#if !ENABLE_NEW_EXIT_HIERARCHY_BEHAVIOR - if (![self supportsRangeManagedInterfaceState]) { - self.interfaceState = ASInterfaceStateNone; - return; - } -#endif - if (ASInterfaceStateIncludesVisible(self.pendingInterfaceState)) { - void(^exitVisibleInterfaceState)(void) = ^{ - // This block intentionally retains self. - __instanceLock__.lock(); - unsigned isStillInHierarchy = _flags.isInHierarchy; - BOOL isVisible = ASInterfaceStateIncludesVisible(_pendingInterfaceState); - ASInterfaceState newState = (_pendingInterfaceState & ~ASInterfaceStateVisible); - // layer may be thrashed, we need to remember the state so we can reset if it enters in same runloop later. - _preExitingInterfaceState = _pendingInterfaceState; - __instanceLock__.unlock(); - if (!isStillInHierarchy && isVisible) { -#if ENABLE_NEW_EXIT_HIERARCHY_BEHAVIOR - if (![self supportsRangeManagedInterfaceState]) { - newState = ASInterfaceStateNone; - } -#endif - self.interfaceState = newState; - } - }; - - if (!ASCATransactionQueueGet().enabled) { - dispatch_async(dispatch_get_main_queue(), exitVisibleInterfaceState); - } else { - exitVisibleInterfaceState(); - } - } -} - -#pragma mark - Interface State - -/** - * We currently only set interface state on nodes in table/collection views. For other nodes, if they are - * in the hierarchy we enable all ASInterfaceState types with `ASInterfaceStateInHierarchy`, otherwise `None`. - */ -- (BOOL)supportsRangeManagedInterfaceState -{ - MutexLocker l(__instanceLock__); - return ASHierarchyStateIncludesRangeManaged(_hierarchyState); -} - -- (void)enterInterfaceState:(ASInterfaceState)interfaceState -{ - if (interfaceState == ASInterfaceStateNone) { - return; // This method is a no-op with a 0-bitfield argument, so don't bother recursing. - } - ASDisplayNodePerformBlockOnEveryNode(nil, self, YES, ^(ASDisplayNode *node) { - node.interfaceState |= interfaceState; - }); -} - -- (void)exitInterfaceState:(ASInterfaceState)interfaceState -{ - if (interfaceState == ASInterfaceStateNone) { - return; // This method is a no-op with a 0-bitfield argument, so don't bother recursing. - } - ASDisplayNodeLogEvent(self, @"%s %@", sel_getName(_cmd), NSStringFromASInterfaceState(interfaceState)); - ASDisplayNodePerformBlockOnEveryNode(nil, self, YES, ^(ASDisplayNode *node) { - node.interfaceState &= (~interfaceState); - }); -} - -- (void)recursivelySetInterfaceState:(ASInterfaceState)newInterfaceState -{ - // Instead of each node in the recursion assuming it needs to schedule itself for display, - // setInterfaceState: skips this when handling range-managed nodes (our whole subtree has this set). - // If our range manager intends for us to be displayed right now, and didn't before, get started! - BOOL shouldScheduleDisplay = [self supportsRangeManagedInterfaceState] && [self shouldScheduleDisplayWithNewInterfaceState:newInterfaceState]; - ASDisplayNodePerformBlockOnEveryNode(nil, self, YES, ^(ASDisplayNode *node) { - node.interfaceState = newInterfaceState; - }); - if (shouldScheduleDisplay) { - [ASDisplayNode scheduleNodeForRecursiveDisplay:self]; - } -} - -- (ASInterfaceState)interfaceState -{ - MutexLocker l(__instanceLock__); - return _interfaceState; -} - -- (void)setInterfaceState:(ASInterfaceState)newState -{ - if (!ASCATransactionQueueGet().enabled) { - [self applyPendingInterfaceState:newState]; - } else { - MutexLocker l(__instanceLock__); - if (_pendingInterfaceState != newState) { - _pendingInterfaceState = newState; - [ASCATransactionQueueGet() enqueue:self]; - } - } -} - -- (ASInterfaceState)pendingInterfaceState -{ - MutexLocker l(__instanceLock__); - return _pendingInterfaceState; -} - -- (void)applyPendingInterfaceState:(ASInterfaceState)newPendingState -{ - //This method is currently called on the main thread. The assert has been added here because all of the - //did(Enter|Exit)(Display|Visible|Preload)State methods currently guarantee calling on main. - ASDisplayNodeAssertMainThread(); - - // This method manages __instanceLock__ itself, to ensure the lock is not held while didEnter/Exit(.*)State methods are called, thus avoid potential deadlocks - ASAssertUnlocked(__instanceLock__); - - ASInterfaceState oldState = ASInterfaceStateNone; - ASInterfaceState newState = ASInterfaceStateNone; - { - MutexLocker l(__instanceLock__); - // newPendingState will not be used when ASCATransactionQueue is enabled - // and use _pendingInterfaceState instead for interfaceState update. - if (!ASCATransactionQueueGet().enabled) { - _pendingInterfaceState = newPendingState; - } - oldState = _interfaceState; - newState = _pendingInterfaceState; - if (newState == oldState) { - return; - } - _interfaceState = newState; - _preExitingInterfaceState = ASInterfaceStateNone; - } - - // It should never be possible for a node to be visible but not be allowed / expected to display. - ASDisplayNodeAssertFalse(ASInterfaceStateIncludesVisible(newState) && !ASInterfaceStateIncludesDisplay(newState)); - - // TODO: Trigger asynchronous measurement if it is not already cached or being calculated. - // if ((newState & ASInterfaceStateMeasureLayout) != (oldState & ASInterfaceStateMeasureLayout)) { - // } - - // For the Preload and Display ranges, we don't want to call -clear* if not being managed by a range controller. - // Otherwise we get flashing behavior from normal UIKit manipulations like navigation controller push / pop. - // Still, the interfaceState should be updated to the current state of the node; just don't act on the transition. - - // Entered or exited data loading state. - BOOL nowPreload = ASInterfaceStateIncludesPreload(newState); - BOOL wasPreload = ASInterfaceStateIncludesPreload(oldState); - - if (nowPreload != wasPreload) { - if (nowPreload) { - [self _didEnterPreloadState]; - } else { - // We don't want to call -didExitPreloadState on nodes that aren't being managed by a range controller. - // Otherwise we get flashing behavior from normal UIKit manipulations like navigation controller push / pop. - if ([self supportsRangeManagedInterfaceState]) { - [self _didExitPreloadState]; - } - } - } - - // Entered or exited contents rendering state. - BOOL nowDisplay = ASInterfaceStateIncludesDisplay(newState); - BOOL wasDisplay = ASInterfaceStateIncludesDisplay(oldState); - - if (nowDisplay != wasDisplay) { - if ([self supportsRangeManagedInterfaceState]) { - if (nowDisplay) { - // Once the working window is eliminated (ASRangeHandlerRender), trigger display directly here. - [self setDisplaySuspended:NO]; - } else { - [self setDisplaySuspended:YES]; - //schedule clear contents on next runloop - dispatch_async(dispatch_get_main_queue(), ^{ - __instanceLock__.lock(); - ASInterfaceState interfaceState = _interfaceState; - __instanceLock__.unlock(); - if (ASInterfaceStateIncludesDisplay(interfaceState) == NO) { - [self clearContents]; - } - }); - } - } else { - // NOTE: This case isn't currently supported as setInterfaceState: isn't exposed externally, and all - // internal use cases are range-managed. When a node is visible, don't mess with display - CA will start it. - if (!ASInterfaceStateIncludesVisible(newState)) { - // Check _implementsDisplay purely for efficiency - it's faster even than calling -asyncLayer. - if ([self _implementsDisplay]) { - if (nowDisplay) { - [ASDisplayNode scheduleNodeForRecursiveDisplay:self]; - } else { - [[self asyncLayer] cancelAsyncDisplay]; - //schedule clear contents on next runloop - dispatch_async(dispatch_get_main_queue(), ^{ - __instanceLock__.lock(); - ASInterfaceState interfaceState = _interfaceState; - __instanceLock__.unlock(); - if (ASInterfaceStateIncludesDisplay(interfaceState) == NO) { - [self clearContents]; - } - }); - } - } - } - } - - if (nowDisplay) { - [self _didEnterDisplayState]; - } else { - [self _didExitDisplayState]; - } - } - - // Became visible or invisible. When range-managed, this represents literal visibility - at least one pixel - // is onscreen. If not range-managed, we can't guarantee more than the node being present in an onscreen window. - BOOL nowVisible = ASInterfaceStateIncludesVisible(newState); - BOOL wasVisible = ASInterfaceStateIncludesVisible(oldState); - - if (nowVisible != wasVisible) { - if (nowVisible) { - [self _didEnterVisibleState]; - } else { - [self _didExitVisibleState]; - } - } - - // Log this change, unless it's just the node going from {} -> {Measure} because that change happens - // for all cell nodes and it isn't currently meaningful. - BOOL measureChangeOnly = ((oldState | newState) == ASInterfaceStateMeasureLayout); - if (!measureChangeOnly) { - } - - ASDisplayNodeLogEvent(self, @"interfaceStateDidChange: %@", NSStringFromASInterfaceStateChange(oldState, newState)); - [self _interfaceStateDidChange:newState fromState:oldState]; -} - -- (void)prepareForCATransactionCommit -{ - // Apply _pendingInterfaceState actual _interfaceState, note that ASInterfaceStateNone is not used. - [self applyPendingInterfaceState:ASInterfaceStateNone]; -} - -- (void)_interfaceStateDidChange:(ASInterfaceState)newState fromState:(ASInterfaceState)oldState -{ - ASAssertUnlocked(__instanceLock__); - ASDisplayNodeAssertMainThread(); - [self interfaceStateDidChange:newState fromState:oldState]; - [self enumerateInterfaceStateDelegates:^(id del) { - [del interfaceStateDidChange:newState fromState:oldState]; - }]; -} - -- (BOOL)shouldScheduleDisplayWithNewInterfaceState:(ASInterfaceState)newInterfaceState -{ - BOOL willDisplay = ASInterfaceStateIncludesDisplay(newInterfaceState); - BOOL nowDisplay = ASInterfaceStateIncludesDisplay(self.interfaceState); - return willDisplay && (willDisplay != nowDisplay); -} - -- (void)addInterfaceStateDelegate:(id )interfaceStateDelegate -{ - MutexLocker l(__instanceLock__); - _hasHadInterfaceStateDelegates = YES; - for (int i = 0; i < AS_MAX_INTERFACE_STATE_DELEGATES; i++) { - if (_interfaceStateDelegates[i] == nil) { - _interfaceStateDelegates[i] = interfaceStateDelegate; - return; - } - } - ASDisplayNodeFailAssert(@"Exceeded interface state delegate limit: %d", AS_MAX_INTERFACE_STATE_DELEGATES); -} - -- (void)removeInterfaceStateDelegate:(id )interfaceStateDelegate -{ - MutexLocker l(__instanceLock__); - for (int i = 0; i < AS_MAX_INTERFACE_STATE_DELEGATES; i++) { - if (_interfaceStateDelegates[i] == interfaceStateDelegate) { - _interfaceStateDelegates[i] = nil; - break; - } - } -} - -- (BOOL)isVisible -{ - MutexLocker l(__instanceLock__); - return ASInterfaceStateIncludesVisible(_interfaceState); -} - -- (void)_didEnterVisibleState -{ - ASDisplayNodeAssertMainThread(); - -#if ASDISPLAYNODE_ASSERTIONS_ENABLED - // Rasterized node's loading state is merged with root node of rasterized tree. - if (!(self.hierarchyState & ASHierarchyStateRasterized)) { - ASDisplayNodeAssert(self.isNodeLoaded, @"Node should be loaded before entering visible state."); - } -#endif - - ASAssertUnlocked(__instanceLock__); - [self didEnterVisibleState]; - [self enumerateInterfaceStateDelegates:^(id del) { - [del didEnterVisibleState]; - }]; - -#if AS_ENABLE_TIPS - [ASTipsController.shared nodeDidAppear:self]; -#endif -} - -- (void)_didExitVisibleState -{ - ASDisplayNodeAssertMainThread(); - ASAssertUnlocked(__instanceLock__); - [self didExitVisibleState]; - [self enumerateInterfaceStateDelegates:^(id del) { - [del didExitVisibleState]; - }]; -} - -- (BOOL)isInDisplayState -{ - MutexLocker l(__instanceLock__); - return ASInterfaceStateIncludesDisplay(_interfaceState); -} - -- (void)_didEnterDisplayState -{ - ASDisplayNodeAssertMainThread(); - ASAssertUnlocked(__instanceLock__); - [self didEnterDisplayState]; - [self enumerateInterfaceStateDelegates:^(id del) { - [del didEnterDisplayState]; - }]; -} - -- (void)_didExitDisplayState -{ - ASDisplayNodeAssertMainThread(); - ASAssertUnlocked(__instanceLock__); - [self didExitDisplayState]; - [self enumerateInterfaceStateDelegates:^(id del) { - [del didExitDisplayState]; - }]; -} - -- (BOOL)isInPreloadState -{ - MutexLocker l(__instanceLock__); - return ASInterfaceStateIncludesPreload(_interfaceState); -} - -- (void)setNeedsPreload -{ - if (self.isInPreloadState) { - [self recursivelyPreload]; - } -} - -- (void)recursivelyPreload -{ - ASPerformBlockOnMainThread(^{ - ASDisplayNodePerformBlockOnEveryNode(nil, self, YES, ^(ASDisplayNode * _Nonnull node) { - [node didEnterPreloadState]; - }); - }); -} - -- (void)recursivelyClearPreloadedData -{ - ASPerformBlockOnMainThread(^{ - ASDisplayNodePerformBlockOnEveryNode(nil, self, YES, ^(ASDisplayNode * _Nonnull node) { - [node didExitPreloadState]; - }); - }); -} - -- (void)_didEnterPreloadState -{ - ASDisplayNodeAssertMainThread(); - ASAssertUnlocked(__instanceLock__); - [self didEnterPreloadState]; - - // If this node has ASM enabled and is not yet visible, force a layout pass to apply its applicable pending layout, if any, - // so that its subnodes are inserted/deleted and start preloading right away. - // - // - If it has an up-to-date layout (and subnodes), calling -layoutIfNeeded will be fast. - // - // - If it doesn't have a calculated or pending layout that fits its current bounds, a measurement pass will occur - // (see -__layout and -_u_measureNodeWithBoundsIfNecessary:). This scenario is uncommon, - // and running a measurement pass here is a fine trade-off because preloading any time after this point would be late. - - if (self.automaticallyManagesSubnodes && !ASActivateExperimentalFeature(ASExperimentalDidEnterPreloadSkipASMLayout)) { - [self layoutIfNeeded]; - } - [self enumerateInterfaceStateDelegates:^(id del) { - [del didEnterPreloadState]; - }]; -} - -- (void)_didExitPreloadState -{ - ASDisplayNodeAssertMainThread(); - ASAssertUnlocked(__instanceLock__); - [self didExitPreloadState]; - [self enumerateInterfaceStateDelegates:^(id del) { - [del didExitPreloadState]; - }]; -} - -- (void)clearContents -{ - ASDisplayNodeAssertMainThread(); - ASAssertUnlocked(__instanceLock__); - - MutexLocker l(__instanceLock__); - if (_flags.canClearContentsOfLayer) { - // No-op if these haven't been created yet, as that guarantees they don't have contents that needs to be released. - _layer.contents = nil; - } - - _placeholderLayer.contents = nil; - _placeholderImage = nil; -} - -- (void)recursivelyClearContents -{ - ASPerformBlockOnMainThread(^{ - ASDisplayNodePerformBlockOnEveryNode(nil, self, YES, ^(ASDisplayNode * _Nonnull node) { - [node clearContents]; - }); - }); -} - -- (void)enumerateInterfaceStateDelegates:(void (NS_NOESCAPE ^)(id))block -{ - ASAssertUnlocked(__instanceLock__); - - id dels[AS_MAX_INTERFACE_STATE_DELEGATES]; - int count = 0; - { - ASLockScopeSelf(); - // Fast path for non-delegating nodes. - if (!_hasHadInterfaceStateDelegates) { - return; - } - - for (int i = 0; i < AS_MAX_INTERFACE_STATE_DELEGATES; i++) { - if ((dels[count] = _interfaceStateDelegates[i])) { - count++; - } - } - } - for (int i = 0; i < count; i++) { - block(dels[i]); - } -} - -#pragma mark - Gesture Recognizing - -- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event -{ - // Subclass hook -} - -- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event -{ - // Subclass hook -} - -- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event -{ - // Subclass hook -} - -- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event -{ - // Subclass hook -} - -- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer -{ - // This method is only implemented on UIView on iOS 6+. - ASDisplayNodeAssertMainThread(); - - // No locking needed as it's main thread only - UIView *view = _view; - if (view == nil) { - return YES; - } - - // If we reach the base implementation, forward up the view hierarchy. - UIView *superview = view.superview; - return [superview gestureRecognizerShouldBegin:gestureRecognizer]; -} - -- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event -{ - ASDisplayNodeAssertMainThread(); - return [_view hitTest:point withEvent:event]; -} - -- (void)setHitTestSlop:(UIEdgeInsets)hitTestSlop -{ - MutexLocker l(__instanceLock__); - _hitTestSlop = hitTestSlop; -} - -- (UIEdgeInsets)hitTestSlop -{ - MutexLocker l(__instanceLock__); - return _hitTestSlop; -} - -- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event -{ - ASDisplayNodeAssertMainThread(); - UIEdgeInsets slop = self.hitTestSlop; - if (_view && UIEdgeInsetsEqualToEdgeInsets(slop, UIEdgeInsetsZero)) { - // Safer to use UIView's -pointInside:withEvent: if we can. - return [_view pointInside:point withEvent:event]; - } else { - return CGRectContainsPoint(UIEdgeInsetsInsetRect(self.bounds, slop), point); - } -} - - -#pragma mark - Pending View State - -- (void)_locked_applyPendingStateToViewOrLayer -{ - ASDisplayNodeAssertMainThread(); - ASAssertLocked(__instanceLock__); - ASDisplayNodeAssert(self.nodeLoaded, @"must have a view or layer"); - - TIME_SCOPED(_debugTimeToApplyPendingState); - - // If no view/layer properties were set before the view/layer were created, _pendingViewState will be nil and the default values - // for the view/layer are still valid. - [self _locked_applyPendingViewState]; - - if (_flags.displaySuspended) { - self._locked_asyncLayer.displaySuspended = YES; - } - if (!_flags.displaysAsynchronously) { - self._locked_asyncLayer.displaysAsynchronously = NO; - } -} - -- (void)applyPendingViewState -{ - ASDisplayNodeAssertMainThread(); - ASAssertUnlocked(__instanceLock__); - - AS::UniqueLock l(__instanceLock__); - // FIXME: Ideally we'd call this as soon as the node receives -setNeedsLayout - // but automatic subnode management would require us to modify the node tree - // in the background on a loaded node, which isn't currently supported. - if (_pendingViewState.hasSetNeedsLayout) { - // Need to unlock before calling setNeedsLayout to avoid deadlocks. - l.unlock(); - [self __setNeedsLayout]; - l.lock(); - } - - [self _locked_applyPendingViewState]; -} - -- (void)_locked_applyPendingViewState -{ - ASDisplayNodeAssertMainThread(); - ASAssertLocked(__instanceLock__); - ASDisplayNodeAssert([self _locked_isNodeLoaded], @"Expected node to be loaded before applying pending state."); - - if (_flags.layerBacked) { - [_pendingViewState applyToLayer:_layer]; - } else { - BOOL specialPropertiesHandling = ASDisplayNodeNeedsSpecialPropertiesHandling(checkFlag(Synchronous), _flags.layerBacked); - [_pendingViewState applyToView:_view withSpecialPropertiesHandling:specialPropertiesHandling]; - } - - // _ASPendingState objects can add up very quickly when adding - // many nodes. This is especially an issue in large collection views - // and table views. This needs to be weighed against the cost of - // reallocing a _ASPendingState. So in range managed nodes we - // delete the pending state, otherwise we just clear it. - if (ASHierarchyStateIncludesRangeManaged(_hierarchyState)) { - _pendingViewState = nil; - } else { - [_pendingViewState clearChanges]; - } -} - -// This method has proved helpful in a few rare scenarios, similar to a category extension on UIView, but assumes knowledge of _ASDisplayView. -// It's considered private API for now and its use should not be encouraged. -- (ASDisplayNode *)_supernodeWithClass:(Class)supernodeClass checkViewHierarchy:(BOOL)checkViewHierarchy -{ - ASDisplayNode *supernode = self.supernode; - while (supernode) { - if ([supernode isKindOfClass:supernodeClass]) - return supernode; - supernode = supernode.supernode; - } - if (!checkViewHierarchy) { - return nil; - } - - UIView *view = self.view.superview; - while (view) { - ASDisplayNode *viewNode = ((_ASDisplayView *)view).asyncdisplaykit_node; - if (viewNode) { - if ([viewNode isKindOfClass:supernodeClass]) - return viewNode; - } - - view = view.superview; - } - - return nil; -} - -#pragma mark - Performance Measurement - -- (void)setMeasurementOptions:(ASDisplayNodePerformanceMeasurementOptions)measurementOptions -{ - MutexLocker l(__instanceLock__); - _measurementOptions = measurementOptions; -} - -- (ASDisplayNodePerformanceMeasurementOptions)measurementOptions -{ - MutexLocker l(__instanceLock__); - return _measurementOptions; -} - -- (ASDisplayNodePerformanceMeasurements)performanceMeasurements -{ - MutexLocker l(__instanceLock__); - ASDisplayNodePerformanceMeasurements measurements = { .layoutSpecNumberOfPasses = -1, .layoutSpecTotalTime = NAN, .layoutComputationNumberOfPasses = -1, .layoutComputationTotalTime = NAN }; - if (_measurementOptions & ASDisplayNodePerformanceMeasurementOptionLayoutSpec) { - measurements.layoutSpecNumberOfPasses = _layoutSpecNumberOfPasses; - measurements.layoutSpecTotalTime = _layoutSpecTotalTime; - } - if (_measurementOptions & ASDisplayNodePerformanceMeasurementOptionLayoutComputation) { - measurements.layoutComputationNumberOfPasses = _layoutComputationNumberOfPasses; - measurements.layoutComputationTotalTime = _layoutComputationTotalTime; - } - return measurements; -} - -#pragma mark - Accessibility - -- (void)setIsAccessibilityContainer:(BOOL)isAccessibilityContainer -{ - MutexLocker l(__instanceLock__); - _isAccessibilityContainer = isAccessibilityContainer; -} - -- (BOOL)isAccessibilityContainer -{ - MutexLocker l(__instanceLock__); - return _isAccessibilityContainer; -} - -- (NSString *)defaultAccessibilityLabel -{ - return nil; -} - -- (NSString *)defaultAccessibilityHint -{ - return nil; -} - -- (NSString *)defaultAccessibilityValue -{ - return nil; -} - -- (NSString *)defaultAccessibilityIdentifier -{ - return nil; -} - -- (UIAccessibilityTraits)defaultAccessibilityTraits -{ - return UIAccessibilityTraitNone; -} - -#pragma mark - Debugging (Private) - -#if ASEVENTLOG_ENABLE -- (ASEventLog *)eventLog -{ - return _eventLog; -} -#endif - -- (NSMutableArray *)propertiesForDescription -{ - NSMutableArray *result = [NSMutableArray array]; - ASPushMainThreadAssertionsDisabled(); - - NSString *debugName = self.debugName; - if (debugName.length > 0) { - [result addObject:@{ (id)kCFNull : ASStringWithQuotesIfMultiword(debugName) }]; - } - - NSString *axId = self.accessibilityIdentifier; - if (axId.length > 0) { - [result addObject:@{ (id)kCFNull : ASStringWithQuotesIfMultiword(axId) }]; - } - - ASPopMainThreadAssertionsDisabled(); - return result; -} - -- (NSMutableArray *)propertiesForDebugDescription -{ - NSMutableArray *result = [NSMutableArray array]; - - if (self.debugName.length > 0) { - [result addObject:@{ @"debugName" : ASStringWithQuotesIfMultiword(self.debugName)}]; - } - if (self.accessibilityIdentifier.length > 0) { - [result addObject:@{ @"axId": ASStringWithQuotesIfMultiword(self.accessibilityIdentifier) }]; - } - - CGRect windowFrame = [self _frameInWindow]; - if (CGRectIsNull(windowFrame) == NO) { - [result addObject:@{ @"frameInWindow" : [NSValue valueWithCGRect:windowFrame] }]; - } - - // Attempt to find view controller. - // Note that the convenience method asdk_associatedViewController has an assertion - // that it's run on main. Since this is a debug method, let's bypass the assertion - // and run up the chain ourselves. - if (_view != nil) { - for (UIResponder *responder in [_view asdk_responderChainEnumerator]) { - UIViewController *vc = ASDynamicCast(responder, UIViewController); - if (vc) { - [result addObject:@{ @"viewController" : ASObjectDescriptionMakeTiny(vc) }]; - break; - } - } - } - - if (_view != nil) { - [result addObject:@{ @"alpha" : @(_view.alpha) }]; - [result addObject:@{ @"frame" : [NSValue valueWithCGRect:_view.frame] }]; - } else if (_layer != nil) { - [result addObject:@{ @"alpha" : @(_layer.opacity) }]; - [result addObject:@{ @"frame" : [NSValue valueWithCGRect:_layer.frame] }]; - } else if (_pendingViewState != nil) { - [result addObject:@{ @"alpha" : @(_pendingViewState.alpha) }]; - [result addObject:@{ @"frame" : [NSValue valueWithCGRect:_pendingViewState.frame] }]; - } -#ifndef MINIMAL_ASDK - // Check supernode so that if we are a cell node we don't find self. - ASCellNode *cellNode = [self supernodeOfClass:[ASCellNode class] includingSelf:NO]; - if (cellNode != nil) { - [result addObject:@{ @"cellNode" : ASObjectDescriptionMakeTiny(cellNode) }]; - } -#endif - - [result addObject:@{ @"interfaceState" : NSStringFromASInterfaceState(self.interfaceState)} ]; - - if (_view != nil) { - [result addObject:@{ @"view" : ASObjectDescriptionMakeTiny(_view) }]; - } else if (_layer != nil) { - [result addObject:@{ @"layer" : ASObjectDescriptionMakeTiny(_layer) }]; - } else if (_viewClass != nil) { - [result addObject:@{ @"viewClass" : _viewClass }]; - } else if (_layerClass != nil) { - [result addObject:@{ @"layerClass" : _layerClass }]; - } else if (_viewBlock != nil) { - [result addObject:@{ @"viewBlock" : _viewBlock }]; - } else if (_layerBlock != nil) { - [result addObject:@{ @"layerBlock" : _layerBlock }]; - } - -#if TIME_DISPLAYNODE_OPS - NSString *creationTypeString = [NSString stringWithFormat:@"cr8:%.2lfms dl:%.2lfms ap:%.2lfms ad:%.2lfms", 1000 * _debugTimeToCreateView, 1000 * _debugTimeForDidLoad, 1000 * _debugTimeToApplyPendingState, 1000 * _debugTimeToAddSubnodeViews]; - [result addObject:@{ @"creationTypeString" : creationTypeString }]; -#endif - - return result; -} - -- (NSString *)description -{ - return ASObjectDescriptionMake(self, [self propertiesForDescription]); -} - -- (NSString *)debugDescription -{ - ASPushMainThreadAssertionsDisabled(); - const auto result = ASObjectDescriptionMake(self, [self propertiesForDebugDescription]); - ASPopMainThreadAssertionsDisabled(); - return result; -} - -// This should only be called for debugging. It's not thread safe and it doesn't assert. -// NOTE: Returns CGRectNull if the node isn't in a hierarchy. -- (CGRect)_frameInWindow -{ - if (self.isNodeLoaded == NO || self.isInHierarchy == NO) { - return CGRectNull; - } - - if (self.layerBacked) { - CALayer *rootLayer = _layer; - CALayer *nextLayer = nil; - while ((nextLayer = rootLayer.superlayer) != nil) { - rootLayer = nextLayer; - } - - return [_layer convertRect:self.threadSafeBounds toLayer:rootLayer]; - } else { - return [_view convertRect:self.threadSafeBounds toView:nil]; - } -} - -@end - -#pragma mark - ASDisplayNode (Debugging) - -@implementation ASDisplayNode (Debugging) - -+ (void)setShouldStoreUnflattenedLayouts:(BOOL)shouldStore -{ - storesUnflattenedLayouts.store(shouldStore); -} - -+ (BOOL)shouldStoreUnflattenedLayouts -{ - return storesUnflattenedLayouts.load(); -} - -- (ASLayout *)unflattenedCalculatedLayout -{ - MutexLocker l(__instanceLock__); - return _unflattenedLayout; -} - -+ (void)setSuppressesInvalidCollectionUpdateExceptions:(BOOL)suppresses -{ - suppressesInvalidCollectionUpdateExceptions.store(suppresses); -} - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-implementations" -+ (BOOL)suppressesInvalidCollectionUpdateExceptions -{ - return suppressesInvalidCollectionUpdateExceptions.load(); -} -#pragma clang diagnostic pop - -- (NSString *)displayNodeRecursiveDescription -{ - return [self _recursiveDescriptionHelperWithIndent:@""]; -} - -- (NSString *)_recursiveDescriptionHelperWithIndent:(NSString *)indent -{ - NSMutableString *subtree = [[[indent stringByAppendingString:self.debugDescription] stringByAppendingString:@"\n"] mutableCopy]; - for (ASDisplayNode *n in self.subnodes) { - [subtree appendString:[n _recursiveDescriptionHelperWithIndent:[indent stringByAppendingString:@" | "]]]; - } - return subtree; -} - -- (NSString *)detailedLayoutDescription -{ - ASPushMainThreadAssertionsDisabled(); - MutexLocker l(__instanceLock__); - const auto props = [[NSMutableArray alloc] init]; - - [props addObject:@{ @"layoutVersion": @(_layoutVersion.load()) }]; - [props addObject:@{ @"bounds": [NSValue valueWithCGRect:self.bounds] }]; - - if (_calculatedDisplayNodeLayout.layout) { - [props addObject:@{ @"calculatedLayout": _calculatedDisplayNodeLayout.layout }]; - [props addObject:@{ @"calculatedVersion": @(_calculatedDisplayNodeLayout.version) }]; - [props addObject:@{ @"calculatedConstrainedSize" : NSStringFromASSizeRange(_calculatedDisplayNodeLayout.constrainedSize) }]; - if (_calculatedDisplayNodeLayout.requestedLayoutFromAbove) { - [props addObject:@{ @"calculatedRequestedLayoutFromAbove": @"YES" }]; - } - } - if (_pendingDisplayNodeLayout.layout) { - [props addObject:@{ @"pendingLayout": _pendingDisplayNodeLayout.layout }]; - [props addObject:@{ @"pendingVersion": @(_pendingDisplayNodeLayout.version) }]; - [props addObject:@{ @"pendingConstrainedSize" : NSStringFromASSizeRange(_pendingDisplayNodeLayout.constrainedSize) }]; - if (_pendingDisplayNodeLayout.requestedLayoutFromAbove) { - [props addObject:@{ @"pendingRequestedLayoutFromAbove": (id)kCFNull }]; - } - } - - ASPopMainThreadAssertionsDisabled(); - return ASObjectDescriptionMake(self, props); -} - -@end - -#pragma mark - ASDisplayNode UIKit / CA Categories - -// We use associated objects as a last resort if our view is not a _ASDisplayView ie it doesn't have the _node ivar to write to - -static const char *ASDisplayNodeAssociatedNodeKey = "ASAssociatedNode"; - -@implementation UIView (ASDisplayNodeInternal) - -- (void)setAsyncdisplaykit_node:(ASDisplayNode *)node -{ - ASWeakProxy *weakProxy = [ASWeakProxy weakProxyWithTarget:node]; - objc_setAssociatedObject(self, ASDisplayNodeAssociatedNodeKey, weakProxy, OBJC_ASSOCIATION_RETAIN); // Weak reference to avoid cycle, since the node retains the view. -} - -- (ASDisplayNode *)asyncdisplaykit_node -{ - ASWeakProxy *weakProxy = objc_getAssociatedObject(self, ASDisplayNodeAssociatedNodeKey); - return weakProxy.target; -} - -@end - -@implementation CALayer (ASDisplayNodeInternal) - -- (void)setAsyncdisplaykit_node:(ASDisplayNode *)node -{ - ASWeakProxy *weakProxy = [ASWeakProxy weakProxyWithTarget:node]; - objc_setAssociatedObject(self, ASDisplayNodeAssociatedNodeKey, weakProxy, OBJC_ASSOCIATION_RETAIN); // Weak reference to avoid cycle, since the node retains the layer. -} - -- (ASDisplayNode *)asyncdisplaykit_node -{ - ASWeakProxy *weakProxy = objc_getAssociatedObject(self, ASDisplayNodeAssociatedNodeKey); - return weakProxy.target; -} - -@end - -@implementation UIView (AsyncDisplayKit) - -- (void)addSubnode:(ASDisplayNode *)subnode -{ - if (subnode.layerBacked) { - // Call -addSubnode: so that we use the asyncdisplaykit_node path if possible. - [self.layer addSubnode:subnode]; - } else { - ASDisplayNode *selfNode = self.asyncdisplaykit_node; - if (selfNode) { - [selfNode addSubnode:subnode]; - } else { - if (subnode.supernode) { - [subnode removeFromSupernode]; - } - [self addSubview:subnode.view]; - } - } -} - -@end - -@implementation CALayer (AsyncDisplayKit) - -- (void)addSubnode:(ASDisplayNode *)subnode -{ - ASDisplayNode *selfNode = self.asyncdisplaykit_node; - if (selfNode) { - [selfNode addSubnode:subnode]; - } else { - if (subnode.supernode) { - [subnode removeFromSupernode]; - } - [self addSublayer:subnode.layer]; - } -} - -@end diff --git a/submodules/AsyncDisplayKit/Source/ASDisplayNodeExtras.mm b/submodules/AsyncDisplayKit/Source/ASDisplayNodeExtras.mm deleted file mode 100644 index b3a56f4697..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASDisplayNodeExtras.mm +++ /dev/null @@ -1,338 +0,0 @@ -// -// ASDisplayNodeExtras.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import "ASDisplayNodeInternal.h" -#import -#import - -#import -#import - -void ASPerformMainThreadDeallocation(id _Nullable __strong * _Nonnull objectPtr) { - /** - * UIKit components must be deallocated on the main thread. We use this shared - * run loop queue to gradually deallocate them across many turns of the main run loop. - */ - static ASRunLoopQueue *queue; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - queue = [[ASRunLoopQueue alloc] initWithRunLoop:CFRunLoopGetMain() retainObjects:YES handler:nil]; - queue.batchSize = 10; - }); - - if (objectPtr != NULL && *objectPtr != nil) { - // TODO: If ASRunLoopQueue supported an "unsafe_unretained" mode, we could - // transfer the caller's +1 into it and save the retain/release pair. - - // Lock queue while enqueuing and releasing, so that there's no risk - // that the queue will release before we get a chance to release. - [queue lock]; - [queue enqueue:*objectPtr]; // Retain, +1 - *objectPtr = nil; // Release, +0 - [queue unlock]; // (After queue drains), release, -1 - } -} - -void _ASSetDebugNames(Class _Nonnull owningClass, NSString * _Nonnull names, ASDisplayNode * _Nullable object, ...) -{ - NSString *owningClassName = NSStringFromClass(owningClass); - NSArray *nameArray = [names componentsSeparatedByString:@", "]; - va_list args; - va_start(args, object); - NSInteger i = 0; - for (ASDisplayNode *node = object; node != nil; node = va_arg(args, id), i++) { - NSMutableString *symbolName = [nameArray[i] mutableCopy]; - // Remove any `self.` or `_` prefix - [symbolName replaceOccurrencesOfString:@"self." withString:@"" options:NSAnchoredSearch range:NSMakeRange(0, symbolName.length)]; - [symbolName replaceOccurrencesOfString:@"_" withString:@"" options:NSAnchoredSearch range:NSMakeRange(0, symbolName.length)]; - node.debugName = [NSString stringWithFormat:@"%@.%@", owningClassName, symbolName]; - } - ASDisplayNodeCAssert(nameArray.count == i, @"Malformed call to ASSetDebugNames: %@", names); - va_end(args); -} - -ASInterfaceState ASInterfaceStateForDisplayNode(ASDisplayNode *displayNode, UIWindow *window) -{ - ASDisplayNodeCAssert(![displayNode isLayerBacked], @"displayNode must not be layer backed as it may have a nil window"); - if (displayNode && [displayNode supportsRangeManagedInterfaceState]) { - // Directly clear the visible bit if we are not in a window. This means that the interface state is, - // if not already, about to be set to invisible as it is not possible for an element to be visible - // while outside of a window. - ASInterfaceState interfaceState = displayNode.pendingInterfaceState; - return (window == nil ? (interfaceState &= (~ASInterfaceStateVisible)) : interfaceState); - } else { - // For not range managed nodes we might be on our own to try to guess if we're visible. - return (window == nil ? ASInterfaceStateNone : (ASInterfaceStateVisible | ASInterfaceStateDisplay)); - } -} - -ASDisplayNode *ASLayerToDisplayNode(CALayer *layer) -{ - return layer.asyncdisplaykit_node; -} - -ASDisplayNode *ASViewToDisplayNode(UIView *view) -{ - return view.asyncdisplaykit_node; -} - -void ASDisplayNodePerformBlockOnEveryNode(CALayer * _Nullable layer, ASDisplayNode * _Nullable node, BOOL traverseSublayers, void(^block)(ASDisplayNode *node)) -{ - if (!node) { - ASDisplayNodeCAssertNotNil(layer, @"Cannot recursively perform with nil node and nil layer"); - ASDisplayNodeCAssertMainThread(); - node = ASLayerToDisplayNode(layer); - } - - if (node) { - block(node); - } - if (traverseSublayers && !layer && [node isNodeLoaded] && ASDisplayNodeThreadIsMain()) { - layer = node.layer; - } - - if (traverseSublayers && layer && node.rasterizesSubtree == NO) { - /// NOTE: The docs say `sublayers` returns a copy, but it does not. - /// See: http://stackoverflow.com/questions/14854480/collection-calayerarray-0x1ed8faa0-was-mutated-while-being-enumerated - for (CALayer *sublayer in [[layer sublayers] copy]) { - ASDisplayNodePerformBlockOnEveryNode(sublayer, nil, traverseSublayers, block); - } - } else if (node) { - for (ASDisplayNode *subnode in [node subnodes]) { - ASDisplayNodePerformBlockOnEveryNode(nil, subnode, traverseSublayers, block); - } - } -} - -void ASDisplayNodePerformBlockOnEveryNodeBFS(ASDisplayNode *node, void(^block)(ASDisplayNode *node)) -{ - // Queue used to keep track of subnodes while traversing this layout in a BFS fashion. - std::queue queue; - queue.push(node); - - while (!queue.empty()) { - node = queue.front(); - queue.pop(); - - block(node); - - // Add all subnodes to process in next step - for (ASDisplayNode *subnode in node.subnodes) { - queue.push(subnode); - } - } -} - -void ASDisplayNodePerformBlockOnEverySubnode(ASDisplayNode *node, BOOL traverseSublayers, void(^block)(ASDisplayNode *node)) -{ - for (ASDisplayNode *subnode in node.subnodes) { - ASDisplayNodePerformBlockOnEveryNode(nil, subnode, YES, block); - } -} - -ASDisplayNode *ASDisplayNodeFindFirstSupernode(ASDisplayNode *node, BOOL (^block)(ASDisplayNode *node)) -{ - // This function has historically started with `self` but the name suggests - // that it wouldn't. Perhaps we should change the behavior. - for (ASDisplayNode *ancestor in node.supernodesIncludingSelf) { - if (block(ancestor)) { - return ancestor; - } - } - return nil; -} - -__kindof ASDisplayNode *ASDisplayNodeFindFirstSupernodeOfClass(ASDisplayNode *start, Class c) -{ - // This function has historically started with `self` but the name suggests - // that it wouldn't. Perhaps we should change the behavior. - return [start supernodeOfClass:c includingSelf:YES]; -} - -static void _ASCollectDisplayNodes(NSMutableArray *array, CALayer *layer) -{ - ASDisplayNode *node = ASLayerToDisplayNode(layer); - - if (nil != node) { - [array addObject:node]; - } - - for (CALayer *sublayer in layer.sublayers) - _ASCollectDisplayNodes(array, sublayer); -} - -NSArray *ASCollectDisplayNodes(ASDisplayNode *node) -{ - NSMutableArray *list = [[NSMutableArray alloc] init]; - for (CALayer *sublayer in node.layer.sublayers) { - _ASCollectDisplayNodes(list, sublayer); - } - return list; -} - -#pragma mark - Find all subnodes - -static void _ASDisplayNodeFindAllSubnodes(NSMutableArray *array, ASDisplayNode *node, BOOL (^block)(ASDisplayNode *node)) -{ - if (!node) - return; - - for (ASDisplayNode *subnode in node.subnodes) { - if (block(subnode)) { - [array addObject:subnode]; - } - - _ASDisplayNodeFindAllSubnodes(array, subnode, block); - } -} - -NSArray *ASDisplayNodeFindAllSubnodes(ASDisplayNode *start, BOOL (^block)(ASDisplayNode *node)) -{ - NSMutableArray *list = [[NSMutableArray alloc] init]; - _ASDisplayNodeFindAllSubnodes(list, start, block); - return list; -} - -NSArray<__kindof ASDisplayNode *> *ASDisplayNodeFindAllSubnodesOfClass(ASDisplayNode *start, Class c) -{ - return ASDisplayNodeFindAllSubnodes(start, ^(ASDisplayNode *n) { - return [n isKindOfClass:c]; - }); -} - -#pragma mark - Find first subnode - -static ASDisplayNode *_ASDisplayNodeFindFirstNode(ASDisplayNode *startNode, BOOL includeStartNode, BOOL (^block)(ASDisplayNode *node)) -{ - for (ASDisplayNode *subnode in startNode.subnodes) { - ASDisplayNode *foundNode = _ASDisplayNodeFindFirstNode(subnode, YES, block); - if (foundNode) { - return foundNode; - } - } - - if (includeStartNode && block(startNode)) - return startNode; - - return nil; -} - -__kindof ASDisplayNode *ASDisplayNodeFindFirstNode(ASDisplayNode *startNode, BOOL (^block)(ASDisplayNode *node)) -{ - return _ASDisplayNodeFindFirstNode(startNode, YES, block); -} - -__kindof ASDisplayNode *ASDisplayNodeFindFirstSubnode(ASDisplayNode *startNode, BOOL (^block)(ASDisplayNode *node)) -{ - return _ASDisplayNodeFindFirstNode(startNode, NO, block); -} - -__kindof ASDisplayNode *ASDisplayNodeFindFirstSubnodeOfClass(ASDisplayNode *start, Class c) -{ - return ASDisplayNodeFindFirstSubnode(start, ^(ASDisplayNode *n) { - return [n isKindOfClass:c]; - }); -} - -static inline BOOL _ASDisplayNodeIsAncestorOfDisplayNode(ASDisplayNode *possibleAncestor, ASDisplayNode *possibleDescendant) -{ - ASDisplayNode *supernode = possibleDescendant; - while (supernode) { - if (supernode == possibleAncestor) { - return YES; - } - supernode = supernode.supernode; - } - - return NO; -} - -UIWindow * _Nullable ASFindWindowOfLayer(CALayer *layer) -{ - UIView *view = ASFindClosestViewOfLayer(layer); - if (UIWindow *window = ASDynamicCast(view, UIWindow)) { - return window; - } else { - return view.window; - } -} - -UIView * _Nullable ASFindClosestViewOfLayer(CALayer *layer) -{ - while (layer != nil) { - if (UIView *view = ASDynamicCast(layer.delegate, UIView)) { - return view; - } - layer = layer.superlayer; - } - return nil; -} - -ASDisplayNode *ASDisplayNodeFindClosestCommonAncestor(ASDisplayNode *node1, ASDisplayNode *node2) -{ - ASDisplayNode *possibleAncestor = node1; - while (possibleAncestor) { - if (_ASDisplayNodeIsAncestorOfDisplayNode(possibleAncestor, node2)) { - break; - } - possibleAncestor = possibleAncestor.supernode; - } - - ASDisplayNodeCAssertNotNil(possibleAncestor, @"Could not find a common ancestor between node1: %@ and node2: %@", node1, node2); - return possibleAncestor; -} - -ASDisplayNode *ASDisplayNodeUltimateParentOfNode(ASDisplayNode *node) -{ - // node <- supernode on each loop - // previous <- node on each loop where node is not nil - // previous is the final non-nil value of supernode, i.e. the root node - ASDisplayNode *previousNode = node; - while ((node = [node supernode])) { - previousNode = node; - } - return previousNode; -} - -#pragma mark - Placeholders - -UIColor *ASDisplayNodeDefaultPlaceholderColor() -{ - static UIColor *defaultPlaceholderColor; - - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - defaultPlaceholderColor = [UIColor colorWithWhite:0.95 alpha:1.0]; - }); - return defaultPlaceholderColor; -} - -UIColor *ASDisplayNodeDefaultTintColor() -{ - static UIColor *defaultTintColor; - - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - defaultTintColor = [UIColor colorWithRed:0.0 green:0.478 blue:1.0 alpha:1.0]; - }); - return defaultTintColor; -} - -#pragma mark - Hierarchy Notifications - -void ASDisplayNodeDisableHierarchyNotifications(ASDisplayNode *node) -{ - [node __incrementVisibilityNotificationsDisabled]; -} - -void ASDisplayNodeEnableHierarchyNotifications(ASDisplayNode *node) -{ - [node __decrementVisibilityNotificationsDisabled]; -} diff --git a/submodules/AsyncDisplayKit/Source/ASDisplayNodeInternal.h b/submodules/AsyncDisplayKit/Source/ASDisplayNodeInternal.h deleted file mode 100644 index 88b268c696..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASDisplayNodeInternal.h +++ /dev/null @@ -1,407 +0,0 @@ -// -// ASDisplayNodeInternal.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -// -// The following methods are ONLY for use by _ASDisplayLayer, _ASDisplayView, and ASDisplayNode. -// These methods must never be called or overridden by other classes. -// - -#import -#import -#import -#import -#import "ASLayoutTransition.h" -#import -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -@protocol _ASDisplayLayerDelegate; -@class _ASDisplayLayer; -@class _ASPendingState; -@class ASNodeController; -struct ASDisplayNodeFlags; - -BOOL ASDisplayNodeSubclassOverridesSelector(Class subclass, SEL selector); -BOOL ASDisplayNodeNeedsSpecialPropertiesHandling(BOOL isSynchronous, BOOL isLayerBacked); - -/// Get the pending view state for the node, creating one if needed. -_ASPendingState * ASDisplayNodeGetPendingState(ASDisplayNode * node); - -typedef NS_OPTIONS(NSUInteger, ASDisplayNodeMethodOverrides) -{ - ASDisplayNodeMethodOverrideNone = 0, - ASDisplayNodeMethodOverrideTouchesBegan = 1 << 0, - ASDisplayNodeMethodOverrideTouchesCancelled = 1 << 1, - ASDisplayNodeMethodOverrideTouchesEnded = 1 << 2, - ASDisplayNodeMethodOverrideTouchesMoved = 1 << 3, - ASDisplayNodeMethodOverrideLayoutSpecThatFits = 1 << 4, - ASDisplayNodeMethodOverrideCalcLayoutThatFits = 1 << 5, - ASDisplayNodeMethodOverrideCalcSizeThatFits = 1 << 6, - ASDisplayNodeMethodOverrideCanBecomeFirstResponder= 1 << 7, - ASDisplayNodeMethodOverrideBecomeFirstResponder = 1 << 8, - ASDisplayNodeMethodOverrideCanResignFirstResponder= 1 << 9, - ASDisplayNodeMethodOverrideResignFirstResponder = 1 << 10, - ASDisplayNodeMethodOverrideIsFirstResponder = 1 << 11, -}; - -typedef NS_OPTIONS(uint_least32_t, ASDisplayNodeAtomicFlags) -{ - Synchronous = 1 << 0, - YogaLayoutInProgress = 1 << 1, -}; - -// Can be called without the node's lock. Client is responsible for thread safety. -#define _loaded(node) (node->_layer != nil) - -#define checkFlag(flag) ((_atomicFlags.load() & flag) != 0) -// Returns the old value of the flag as a BOOL. -#define setFlag(flag, x) (((x ? _atomicFlags.fetch_or(flag) \ - : _atomicFlags.fetch_and(~flag)) & flag) != 0) - -AS_EXTERN NSString * const ASRenderingEngineDidDisplayScheduledNodesNotification; -AS_EXTERN NSString * const ASRenderingEngineDidDisplayNodesScheduledBeforeTimestamp; - -// Allow 2^n increments of begin disabling hierarchy notifications -#define VISIBILITY_NOTIFICATIONS_DISABLED_BITS 4 - -#define TIME_DISPLAYNODE_OPS 0 // If you're using this information frequently, try: (DEBUG || PROFILE) - -#define NUM_CLIP_CORNER_LAYERS 4 - -@interface ASDisplayNode () <_ASTransitionContextCompletionDelegate> -{ -@package - AS::RecursiveMutex __instanceLock__; - - _ASPendingState *_pendingViewState; - ASInterfaceState _pendingInterfaceState; - ASInterfaceState _preExitingInterfaceState; - - UIView *_view; - CALayer *_layer; - - std::atomic _atomicFlags; - - struct ASDisplayNodeFlags { - // public properties - unsigned viewEverHadAGestureRecognizerAttached:1; - unsigned layerBacked:1; - unsigned displaysAsynchronously:1; - unsigned rasterizesSubtree:1; - unsigned shouldBypassEnsureDisplay:1; - unsigned displaySuspended:1; - unsigned shouldAnimateSizeChanges:1; - - // Wrapped view handling - - // The layer contents should not be cleared in case the node is wrapping a UIImageView.UIImageView is specifically - // optimized for performance and does not use the usual way to provide the contents of the CALayer via the - // CALayerDelegate method that backs the UIImageView. - unsigned canClearContentsOfLayer:1; - - // Prevent calling setNeedsDisplay on a layer that backs a UIImageView. Usually calling setNeedsDisplay on a CALayer - // triggers a recreation of the contents of layer unfortunately calling it on a CALayer that backs a UIImageView - // it goes through the normal flow to assign the contents to a layer via the CALayerDelegate methods. Unfortunately - // UIImageView does not do recreate the layer contents the usual way, it actually does not implement some of the - // methods at all instead it throws away the contents of the layer and nothing will show up. - unsigned canCallSetNeedsDisplayOfLayer:1; - - unsigned implementsDrawRect:1; - unsigned implementsImageDisplay:1; - unsigned implementsDrawParameters:1; - - // internal state - unsigned isEnteringHierarchy:1; - unsigned isExitingHierarchy:1; - unsigned isInHierarchy:1; - unsigned visibilityNotificationsDisabled:VISIBILITY_NOTIFICATIONS_DISABLED_BITS; - unsigned isDeallocating:1; - } _flags; - -@protected - ASDisplayNode * __weak _supernode; - NSMutableArray *_subnodes; - - ASNodeController *_strongNodeController; - __weak ASNodeController *_weakNodeController; - - // Set this to nil whenever you modify _subnodes - NSArray *_cachedSubnodes; - - std::atomic_uint _displaySentinel; - - // This is the desired contentsScale, not the scale at which the layer's contents should be displayed - CGFloat _contentsScaleForDisplay; - ASDisplayNodeMethodOverrides _methodOverrides; - - UIEdgeInsets _hitTestSlop; - -#if ASEVENTLOG_ENABLE - ASEventLog *_eventLog; -#endif - - - // Layout support - ASLayoutElementStyle *_style; - std::atomic _primitiveTraitCollection; - - // Layout Spec - ASLayoutSpecBlock _layoutSpecBlock; - NSString *_debugName; - -#if YOGA - // Only ASDisplayNodes are supported in _yogaChildren currently. This means that it is necessary to - // create ASDisplayNodes to make a stack layout when using Yoga. - // However, the implementation is mostly ready for id , with a few areas requiring updates. - NSMutableArray *_yogaChildren; - __weak ASDisplayNode *_yogaParent; - ASLayout *_yogaCalculatedLayout; -#endif - - // Automatically manages subnodes - BOOL _automaticallyManagesSubnodes; // Main thread only - - // Layout Transition - _ASTransitionContext *_pendingLayoutTransitionContext; - NSTimeInterval _defaultLayoutTransitionDuration; - NSTimeInterval _defaultLayoutTransitionDelay; - UIViewAnimationOptions _defaultLayoutTransitionOptions; - - std::atomic _transitionID; - std::atomic _pendingTransitionID; - ASLayoutTransition *_pendingLayoutTransition; - ASDisplayNodeLayout _calculatedDisplayNodeLayout; - ASDisplayNodeLayout _pendingDisplayNodeLayout; - - /// Sentinel for layout data. Incremented when we get -setNeedsLayout / -invalidateCalculatedLayout. - /// Starts at 1. - std::atomic _layoutVersion; - - - // Layout Spec performance measurement - ASDisplayNodePerformanceMeasurementOptions _measurementOptions; - NSTimeInterval _layoutSpecTotalTime; - NSInteger _layoutSpecNumberOfPasses; - NSTimeInterval _layoutComputationTotalTime; - NSInteger _layoutComputationNumberOfPasses; - - - // View Loading - ASDisplayNodeViewBlock _viewBlock; - ASDisplayNodeLayerBlock _layerBlock; - NSMutableArray *_onDidLoadBlocks; - Class _viewClass; // nil -> _ASDisplayView - Class _layerClass; // nil -> _ASDisplayLayer - - - // Placeholder support - UIImage *_placeholderImage; - BOOL _placeholderEnabled; - CALayer *_placeholderLayer; - - // keeps track of nodes/subnodes that have not finished display, used with placeholders - ASWeakSet *_pendingDisplayNodes; - - - // Corner Radius support - CGFloat _cornerRadius; - ASCornerRoundingType _cornerRoundingType; - CALayer *_clipCornerLayers[NUM_CLIP_CORNER_LAYERS]; - - ASDisplayNodeContextModifier _willDisplayNodeContentWithRenderingContext; - ASDisplayNodeContextModifier _didDisplayNodeContentWithRenderingContext; - - - // Accessibility support - BOOL _isAccessibilityElement; - NSString *_accessibilityLabel; - NSAttributedString *_accessibilityAttributedLabel; - NSString *_accessibilityHint; - NSAttributedString *_accessibilityAttributedHint; - NSString *_accessibilityValue; - NSAttributedString *_accessibilityAttributedValue; - UIAccessibilityTraits _accessibilityTraits; - CGRect _accessibilityFrame; - NSString *_accessibilityLanguage; - BOOL _accessibilityElementsHidden; - BOOL _accessibilityViewIsModal; - BOOL _shouldGroupAccessibilityChildren; - NSString *_accessibilityIdentifier; - UIAccessibilityNavigationStyle _accessibilityNavigationStyle; - NSArray *_accessibilityHeaderElements; - CGPoint _accessibilityActivationPoint; - UIBezierPath *_accessibilityPath; - BOOL _isAccessibilityContainer; - - - // Safe Area support - // These properties are used on iOS 10 and lower, where safe area is not supported by UIKit. - UIEdgeInsets _fallbackSafeAreaInsets; - BOOL _fallbackInsetsLayoutMarginsFromSafeArea; - - BOOL _automaticallyRelayoutOnSafeAreaChanges; - BOOL _automaticallyRelayoutOnLayoutMarginsChanges; - - BOOL _isViewControllerRoot; - - -#pragma mark - ASDisplayNode (Debugging) - ASLayout *_unflattenedLayout; - -#if TIME_DISPLAYNODE_OPS -@public - NSTimeInterval _debugTimeToCreateView; - NSTimeInterval _debugTimeToApplyPendingState; - NSTimeInterval _debugTimeToAddSubnodeViews; - NSTimeInterval _debugTimeForDidLoad; -#endif - - /// Fast path: tells whether we've ever had an interface state delegate before. - BOOL _hasHadInterfaceStateDelegates; - __weak id _interfaceStateDelegates[AS_MAX_INTERFACE_STATE_DELEGATES]; -} - -+ (void)scheduleNodeForRecursiveDisplay:(ASDisplayNode *)node; - -/// The _ASDisplayLayer backing the node, if any. -@property (nullable, nonatomic, readonly) _ASDisplayLayer *asyncLayer; - -/// Bitmask to check which methods an object overrides. -- (ASDisplayNodeMethodOverrides)methodOverrides; - -/** - * Invoked before a call to setNeedsLayout to the underlying view - */ -- (void)__setNeedsLayout; - -/** - * Invoked after a call to setNeedsDisplay to the underlying view - */ -- (void)__setNeedsDisplay; - -/** - * Called whenever the node needs to layout its subnodes and, if it's already loaded, its subviews. Executes the layout pass for the node - * - * This method is thread-safe but asserts thread affinity. - */ -- (void)__layout; - -/** - * Internal method to add / replace / insert subnode and remove from supernode without checking if - * node has automaticallyManagesSubnodes set to YES. - */ -- (void)_addSubnode:(ASDisplayNode *)subnode; -- (void)_replaceSubnode:(ASDisplayNode *)oldSubnode withSubnode:(ASDisplayNode *)replacementSubnode; -- (void)_insertSubnode:(ASDisplayNode *)subnode belowSubnode:(ASDisplayNode *)below; -- (void)_insertSubnode:(ASDisplayNode *)subnode aboveSubnode:(ASDisplayNode *)above; -- (void)_insertSubnode:(ASDisplayNode *)subnode atIndex:(NSInteger)idx; -- (void)_removeFromSupernodeIfEqualTo:(ASDisplayNode *)supernode; -- (void)_removeFromSupernode; - -// Private API for helper functions / unit tests. Use ASDisplayNodeDisableHierarchyNotifications() to control this. -- (BOOL)__visibilityNotificationsDisabled; -- (BOOL)__selfOrParentHasVisibilityNotificationsDisabled; -- (void)__incrementVisibilityNotificationsDisabled; -- (void)__decrementVisibilityNotificationsDisabled; - -// Helper methods for UIResponder forwarding -- (BOOL)__canBecomeFirstResponder; -- (BOOL)__becomeFirstResponder; -- (BOOL)__canResignFirstResponder; -- (BOOL)__resignFirstResponder; -- (BOOL)__isFirstResponder; - -/// Helper method to summarize whether or not the node run through the display process -- (BOOL)_implementsDisplay; - -/// Display the node's view/layer immediately on the current thread, bypassing the background thread rendering. Will be deprecated. -- (void)displayImmediately; - -/// Refreshes any precomposited or drawn clip corners, setting up state as required to transition radius or rounding type. -- (void)updateCornerRoundingWithType:(ASCornerRoundingType)newRoundingType cornerRadius:(CGFloat)newCornerRadius; - -/// Alternative initialiser for backing with a custom view class. Supports asynchronous display with _ASDisplayView subclasses. -- (instancetype)initWithViewClass:(Class)viewClass; - -/// Alternative initialiser for backing with a custom layer class. Supports asynchronous display with _ASDisplayLayer subclasses. -- (instancetype)initWithLayerClass:(Class)layerClass; - -@property (nonatomic) CGFloat contentsScaleForDisplay; - -- (void)applyPendingViewState; - -/** - * Makes a local copy of the interface state delegates then calls the block on each. - * - * Lock is not held during block invocation. Method must not be called with the lock held. - */ -- (void)enumerateInterfaceStateDelegates:(void(NS_NOESCAPE ^)(id delegate))block; - -/** - * // TODO: NOT YET IMPLEMENTED - * - * @abstract Prevents interface state changes from affecting the node, until disabled. - * - * @discussion Useful to avoid flashing after removing a node from the hierarchy and re-adding it. - * Removing a node from the hierarchy will cause it to exit the Display state, clearing its contents. - * For some animations, it's desirable to be able to remove a node without causing it to re-display. - * Once re-enabled, the interface state will be updated to the same value it would have been. - * - * @see ASInterfaceState - */ -@property (nonatomic) BOOL interfaceStateSuspended; - -/** - * This method has proven helpful in a few rare scenarios, similar to a category extension on UIView, - * but it's considered private API for now and its use should not be encouraged. - * @param checkViewHierarchy If YES, and no supernode can be found, method will walk up from `self.view` to find a supernode. - * If YES, this method must be called on the main thread and the node must not be layer-backed. - */ -- (nullable ASDisplayNode *)_supernodeWithClass:(Class)supernodeClass checkViewHierarchy:(BOOL)checkViewHierarchy; - -/** - * Whether this node rasterizes its descendants. See -enableSubtreeRasterization. - */ -@property (readonly) BOOL rasterizesSubtree; - -/** - * Called if a gesture recognizer was attached to an _ASDisplayView - */ -- (void)nodeViewDidAddGestureRecognizer; - -// Recalculates fallbackSafeAreaInsets for the subnodes -- (void)_fallbackUpdateSafeAreaOnChildren; - -@end - -@interface ASDisplayNode (InternalPropertyBridge) - -@property (nonatomic) CGFloat layerCornerRadius; - -- (BOOL)_locked_insetsLayoutMarginsFromSafeArea; - -@end - -@interface ASDisplayNode (ASLayoutElementPrivate) - -/** - * Returns the internal style object or creates a new if no exists. Need to be called with lock held. - */ -- (ASLayoutElementStyle *)_locked_style; - -/** - * Returns the current layout element. Need to be called with lock held. - */ -- (id)_locked_layoutElementThatFits:(ASSizeRange)constrainedSize; - -@end - -NS_ASSUME_NONNULL_END diff --git a/submodules/AsyncDisplayKit/Source/ASDisplayNodeLayout.h b/submodules/AsyncDisplayKit/Source/ASDisplayNodeLayout.h deleted file mode 100644 index af905a813a..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASDisplayNodeLayout.h +++ /dev/null @@ -1,59 +0,0 @@ -// -// ASDisplayNodeLayout.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#pragma once - -#import -#import - -@class ASLayout; - -/* - * Represents a connection between an ASLayout and a ASDisplayNode - * ASDisplayNode uses this to store additional information that are necessary besides the layout - */ -struct ASDisplayNodeLayout { - ASLayout *layout; - ASSizeRange constrainedSize; - CGSize parentSize; - BOOL requestedLayoutFromAbove; - NSUInteger version; - - /* - * Create a new display node layout with - * @param layout The layout to associate, usually returned from a call to -layoutThatFits:parentSize: - * @param constrainedSize Constrained size used to create the layout - * @param parentSize Parent size used to create the layout - * @param version The version of the source layout data – see ASDisplayNode's _layoutVersion. - */ - ASDisplayNodeLayout(ASLayout *layout, ASSizeRange constrainedSize, CGSize parentSize, NSUInteger version) - : layout(layout), constrainedSize(constrainedSize), parentSize(parentSize), requestedLayoutFromAbove(NO), version(version) {}; - - /* - * Creates a layout without any layout associated. By default this display node layout is dirty. - */ - ASDisplayNodeLayout() - : layout(nil), constrainedSize({{0, 0}, {0, 0}}), parentSize({0, 0}), requestedLayoutFromAbove(NO), version(0) {}; - - /** - * Returns whether this is valid for a given version - */ - BOOL isValid(NSUInteger versionArg) { - return layout != nil && version >= versionArg; - } - - /** - * Returns whether this is valid for a given constrained size, parent size, and version - */ - BOOL isValid(ASSizeRange theConstrainedSize, CGSize theParentSize, NSUInteger versionArg) { - return isValid(versionArg) - && CGSizeEqualToSize(parentSize, theParentSize) - && ASSizeRangeEqualToSizeRange(constrainedSize, theConstrainedSize); - } -}; diff --git a/submodules/AsyncDisplayKit/Source/ASEditableTextNode.mm b/submodules/AsyncDisplayKit/Source/ASEditableTextNode.mm deleted file mode 100644 index 87baeb09ac..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASEditableTextNode.mm +++ /dev/null @@ -1,1172 +0,0 @@ -// -// ASEditableTextNode.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#import -#import - -#import -#import -#import -#import "ASTextNodeWordKerner.h" -#import - -@implementation ASEditableTextNodeTargetForAction - -- (instancetype)initWithTarget:(id _Nullable)target { - self = [super init]; - if (self != nil) { - _target = target; - } - return self; -} - -@end - -/** - @abstract Object to hold UITextView's pending UITextInputTraits -**/ -@interface _ASTextInputTraitsPendingState : NSObject - -@property UITextAutocapitalizationType autocapitalizationType; -@property UITextAutocorrectionType autocorrectionType; -@property UITextSpellCheckingType spellCheckingType; -@property UIKeyboardAppearance keyboardAppearance; -@property UIKeyboardType keyboardType; -@property UIReturnKeyType returnKeyType; -@property BOOL enablesReturnKeyAutomatically; -@property (getter=isSecureTextEntry) BOOL secureTextEntry; - -@end - -@implementation _ASTextInputTraitsPendingState - -- (instancetype)init -{ - if (!(self = [super init])) - return nil; - - // set default values, as defined in Apple's comments in UITextInputTraits.h - _autocapitalizationType = UITextAutocapitalizationTypeSentences; - _autocorrectionType = UITextAutocorrectionTypeDefault; - _spellCheckingType = UITextSpellCheckingTypeDefault; - _keyboardAppearance = UIKeyboardAppearanceDefault; - _keyboardType = UIKeyboardTypeDefault; - _returnKeyType = UIReturnKeyDefault; - - return self; -} - -@end - -/** - @abstract As originally reported in rdar://14729288, when scrollEnabled = NO, - UITextView does not calculate its contentSize. This makes it difficult - for a client to embed a UITextView inside a different scroll view with - other content (setting scrollEnabled = NO on the UITextView itself, - because the containing scroll view will handle the gesture)... - because accessing contentSize is typically necessary to perform layout. - Apple later closed the issue as expected behavior. This works around - the issue by ensuring that contentSize is always calculated, while - still providing control over the UITextView's scrolling. - - See issue: https://github.com/facebook/AsyncDisplayKit/issues/1063 - */ - -@interface ASPanningOverriddenUITextView : ASTextKitComponentsTextView -{ - BOOL _shouldBlockPanGesture; -} - -@property (nonatomic, copy) bool (^shouldCopy)(); -@property (nonatomic, copy) bool (^shouldPaste)(); -@property (nonatomic, copy) ASEditableTextNodeTargetForAction *(^targetForActionImpl)(SEL); -@property (nonatomic, copy) bool (^shouldReturn)(); -@property (nonatomic, copy) void (^backspaceWhileEmpty)(); - -@property (nonatomic, strong) NSString * _Nullable initialPrimaryLanguage; -@property (nonatomic) bool initializedPrimaryInputLanguage; - -@end - -@implementation ASPanningOverriddenUITextView - -#if TARGET_OS_IOS - // tvOS doesn't support self.scrollsToTop -- (BOOL)scrollEnabled -{ - return _shouldBlockPanGesture; -} - -- (void)setScrollEnabled:(BOOL)scrollEnabled -{ - _shouldBlockPanGesture = !scrollEnabled; - self.scrollsToTop = scrollEnabled; - - [super setScrollEnabled:YES]; -} - -- (void)setContentSize:(CGSize)contentSize { - [super setContentSize:contentSize]; -} - -- (BOOL)canPerformAction:(SEL)action withSender:(id)sender -{ - if (_targetForActionImpl) { - ASEditableTextNodeTargetForAction *result = _targetForActionImpl(action); - if (result) { - return result.target != nil; - } - } - - if (action == @selector(paste:)) { - NSArray *items = [UIMenuController sharedMenuController].menuItems; - if (((UIMenuItem *)items.firstObject).action == @selector(toggleBoldface:)) { - return false; - } - return true; - } - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wundeclared-selector" - static SEL promptForReplaceSelector; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - promptForReplaceSelector = NSSelectorFromString(@"_promptForReplace:"); - }); - if (action == promptForReplaceSelector) { - return false; - } -#pragma clang diagnostic pop - - if (action == @selector(toggleUnderline:)) { - return false; - } - - return [super canPerformAction:action withSender:sender]; -} - -- (id)targetForAction:(SEL)action withSender:(id)__unused sender -{ - if (_targetForActionImpl) { - ASEditableTextNodeTargetForAction *result = _targetForActionImpl(action); - if (result) { - return result.target; - } - } - return [super targetForAction:action withSender:sender]; -} - -- (void)copy:(id)sender { - if (_shouldCopy == nil || _shouldCopy()) { - [super copy:sender]; - } -} - -- (void)paste:(id)sender -{ - if (_shouldPaste == nil || _shouldPaste()) { - [super paste:sender]; - } -} - -- (NSArray *)keyCommands { - UIKeyCommand *plainReturn = [UIKeyCommand keyCommandWithInput:@"\r" modifierFlags:kNilOptions action:@selector(handlePlainReturn:)]; - return @[ - plainReturn - ]; -} - -- (void)handlePlainReturn:(id)__unused sender { - if (_shouldReturn) { - _shouldReturn(); - } -} - -- (void)deleteBackward { - bool notify = self.text.length == 0; - [super deleteBackward]; - if (notify) { - if (_backspaceWhileEmpty) { - _backspaceWhileEmpty(); - } - } -} - -- (UIKeyboardAppearance)keyboardAppearance { - return [super keyboardAppearance]; -} - -- (UITextInputMode *)textInputMode { - if (!_initializedPrimaryInputLanguage) { - _initializedPrimaryInputLanguage = true; - if (_initialPrimaryLanguage != nil) { - for (UITextInputMode *inputMode in [UITextInputMode activeInputModes]) { - NSString *primaryLanguage = inputMode.primaryLanguage; - if (primaryLanguage != nil && [primaryLanguage isEqualToString:_initialPrimaryLanguage]) { - return inputMode; - } - } - } - } - return [super textInputMode]; -} - -#endif - -- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer -{ - // Never allow our pans to begin when _shouldBlockPanGesture is true. - if (_shouldBlockPanGesture && gestureRecognizer == self.panGestureRecognizer) - return NO; - - // Otherwise, proceed as usual. - if ([UITextView instancesRespondToSelector:_cmd]) - return [super gestureRecognizerShouldBegin:gestureRecognizer]; - return YES; -} - -@end - -#pragma mark - -@interface ASEditableTextNode () -{ - @private - // Configuration. - NSDictionary *_typingAttributes; - - // Core. - id __weak _delegate; - BOOL _delegateDidUpdateEnqueued; - - // TextKit. - AS::RecursiveMutex _textKitLock; - ASTextKitComponents *_textKitComponents; - ASTextKitComponents *_placeholderTextKitComponents; - // Forwards NSLayoutManagerDelegate methods related to word kerning - ASTextNodeWordKerner *_wordKerner; - - // UITextInputTraits - AS::RecursiveMutex _textInputTraitsLock; - _ASTextInputTraitsPendingState *_textInputTraits; - - // Misc. State. - BOOL _displayingPlaceholder; // Defaults to YES. - BOOL _isPreservingSelection; - BOOL _isPreservingText; - BOOL _selectionChangedForEditedText; - NSRange _previousSelectedRange; -} - -@property (nonatomic, readonly) _ASTextInputTraitsPendingState *textInputTraits; - -@end - -@implementation ASEditableTextNode - -#pragma mark - NSObject Overrides -- (instancetype)init -{ - return [self initWithTextKitComponents:[ASTextKitComponents componentsWithAttributedSeedString:nil textContainerSize:CGSizeZero] - placeholderTextKitComponents:[ASTextKitComponents componentsWithAttributedSeedString:nil textContainerSize:CGSizeZero]]; -} - -- (instancetype)initWithTextKitComponents:(ASTextKitComponents *)textKitComponents - placeholderTextKitComponents:(ASTextKitComponents *)placeholderTextKitComponents -{ - if (!(self = [super init])) - return nil; - - _displayingPlaceholder = YES; - _scrollEnabled = YES; - - // Create the scaffolding for the text view. - _textKitComponents = textKitComponents; - _textKitComponents.layoutManager.delegate = self; - _wordKerner = [[ASTextNodeWordKerner alloc] init]; - _textContainerInset = UIEdgeInsetsZero; - - // Create the placeholder scaffolding. - _placeholderTextKitComponents = placeholderTextKitComponents; - _placeholderTextKitComponents.layoutManager.delegate = self; - - return self; -} - -#pragma mark - ASDisplayNode Overrides -- (void)didLoad -{ - [super didLoad]; - - void (^configureTextView)(UITextView *) = ^(UITextView *textView) { - if (!_displayingPlaceholder || textView != _textKitComponents.textView) { - // If showing the placeholder, don't propagate backgroundColor/opaque to the editable textView. It is positioned over the placeholder to accept taps to begin editing, and if it's opaque/colored then it'll obscure the placeholder. - textView.backgroundColor = self.backgroundColor; - textView.opaque = self.opaque; - } else if (_displayingPlaceholder && textView == _textKitComponents.textView) { - // The default backgroundColor for a textView is white. Due to the reason described above, make sure the editable textView starts out transparent. - textView.backgroundColor = nil; - textView.opaque = NO; - } - textView.textContainerInset = self.textContainerInset; - - // Configure textView with UITextInputTraits - { - AS::MutexLocker l(_textInputTraitsLock); - if (_textInputTraits) { - textView.autocapitalizationType = _textInputTraits.autocapitalizationType; - textView.autocorrectionType = _textInputTraits.autocorrectionType; - textView.spellCheckingType = _textInputTraits.spellCheckingType; - textView.keyboardType = _textInputTraits.keyboardType; - textView.keyboardAppearance = _textInputTraits.keyboardAppearance; - textView.returnKeyType = _textInputTraits.returnKeyType; - textView.enablesReturnKeyAutomatically = _textInputTraits.enablesReturnKeyAutomatically; - textView.secureTextEntry = _textInputTraits.isSecureTextEntry; - } - } - - [self.view addSubview:textView]; - }; - - AS::MutexLocker l(_textKitLock); - - // Create and configure the placeholder text view. - _placeholderTextKitComponents.textView = [[ASTextKitComponentsTextView alloc] initWithFrame:CGRectZero textContainer:_placeholderTextKitComponents.textContainer]; - _placeholderTextKitComponents.textView.userInteractionEnabled = NO; - _placeholderTextKitComponents.textView.accessibilityElementsHidden = YES; - configureTextView(_placeholderTextKitComponents.textView); - - // Create and configure our text view. - ASPanningOverriddenUITextView *textView = [[ASPanningOverriddenUITextView alloc] initWithFrame:CGRectZero textContainer:_textKitComponents.textContainer]; - textView.initialPrimaryLanguage = _initialPrimaryLanguage; - __weak ASEditableTextNode *weakSelf = self; - textView.shouldCopy = ^bool{ - __strong ASEditableTextNode *strongSelf = weakSelf; - if (strongSelf != nil) { - if ([strongSelf->_delegate respondsToSelector:@selector(editableTextNodeShouldCopy:)]) { - return [strongSelf->_delegate editableTextNodeShouldCopy:self]; - } - } - return true; - }; - textView.shouldPaste = ^bool{ - __strong ASEditableTextNode *strongSelf = weakSelf; - if (strongSelf != nil) { - if ([strongSelf->_delegate respondsToSelector:@selector(editableTextNodeShouldPaste:)]) { - return [strongSelf->_delegate editableTextNodeShouldPaste:self]; - } - } - return true; - }; - textView.targetForActionImpl = ^id(SEL action) { - __strong ASEditableTextNode *strongSelf = weakSelf; - if (strongSelf != nil) { - if ([strongSelf->_delegate respondsToSelector:@selector(editableTextNodeTargetForAction:)]) { - return [strongSelf->_delegate editableTextNodeTargetForAction:action]; - } - } - return nil; - }; - textView.shouldReturn = ^bool { - __strong ASEditableTextNode *strongSelf = weakSelf; - if (strongSelf != nil) { - if ([strongSelf->_delegate respondsToSelector:@selector(editableTextNodeShouldReturn:)]) { - return [strongSelf->_delegate editableTextNodeShouldReturn:strongSelf]; - } - } - return true; - }; - textView.backspaceWhileEmpty = ^{ - __strong ASEditableTextNode *strongSelf = weakSelf; - if (strongSelf != nil) { - if ([strongSelf->_delegate respondsToSelector:@selector(editableTextNodeBackspaceWhileEmpty:)]) { - [strongSelf->_delegate editableTextNodeBackspaceWhileEmpty:strongSelf]; - } - } - }; - _textKitComponents.textView = textView; - _textKitComponents.textView.scrollEnabled = _scrollEnabled; - _textKitComponents.textView.delegate = self; - #if TARGET_OS_IOS - _textKitComponents.textView.editable = YES; - #endif - _textKitComponents.textView.typingAttributes = _typingAttributes; - _textKitComponents.textView.accessibilityHint = _placeholderTextKitComponents.textStorage.string; - configureTextView(_textKitComponents.textView); - - [self _updateDisplayingPlaceholder]; - - // once view is loaded, setters set directly on view - _textInputTraits = nil; - - UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapGesture:)]; - tapRecognizer.cancelsTouchesInView = false; - tapRecognizer.delaysTouchesBegan = false; - tapRecognizer.delaysTouchesEnded = false; - tapRecognizer.delegate = self; - [self.view addGestureRecognizer:tapRecognizer]; -} - -- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer { - return true; -} - -- (void)tapGesture:(UITapGestureRecognizer *)recognizer { - static Class promptClass = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - promptClass = NSClassFromString([[NSString alloc] initWithFormat:@"%@AutocorrectInlinePrompt", @"UI"]); - }); - - if (recognizer.state == UIGestureRecognizerStateEnded) { - UIView *result = [self hitTest:[recognizer locationInView:self.view] withEvent:nil]; - if (result != nil && [result class] == promptClass) { - [self dropAutocorrection]; - } - } -} - -- (CGSize)calculateSizeThatFits:(CGSize)constrainedSize -{ - ASTextKitComponents *displayedComponents = [self isDisplayingPlaceholder] ? _placeholderTextKitComponents : _textKitComponents; - - CGSize textSize; - - if (_maximumLinesToDisplay > 0) { - textSize = [displayedComponents sizeForConstrainedWidth:constrainedSize.width - forMaxNumberOfLines: _maximumLinesToDisplay]; - } else { - textSize = [displayedComponents sizeForConstrainedWidth:constrainedSize.width]; - } - - CGFloat width = std::ceil(constrainedSize.width); - CGFloat height = std::ceil(textSize.height + _textContainerInset.top + _textContainerInset.bottom); - return CGSizeMake(std::fmin(width, constrainedSize.width), std::fmin(height, constrainedSize.height)); -} - -- (void)layout -{ - ASDisplayNodeAssertMainThread(); - - [super layout]; - [self _layoutTextView]; -} - -- (void)setBackgroundColor:(UIColor *)backgroundColor -{ - [super setBackgroundColor:backgroundColor]; - - AS::MutexLocker l(_textKitLock); - - // If showing the placeholder, don't propagate backgroundColor/opaque to the editable textView. It is positioned over the placeholder to accept taps to begin editing, and if it's opaque/colored then it'll obscure the placeholder. - // The backgroundColor/opaque will be propagated to the editable textView when editing begins. - if (!_displayingPlaceholder) { - _textKitComponents.textView.backgroundColor = backgroundColor; - } - _placeholderTextKitComponents.textView.backgroundColor = backgroundColor; -} - -- (void)setTextContainerInset:(UIEdgeInsets)textContainerInset -{ - AS::MutexLocker l(_textKitLock); - - _textContainerInset = textContainerInset; - _textKitComponents.textView.textContainerInset = textContainerInset; - _placeholderTextKitComponents.textView.textContainerInset = textContainerInset; -} - -- (void)setOpaque:(BOOL)opaque -{ - [super setOpaque:opaque]; - - AS::MutexLocker l(_textKitLock); - - // If showing the placeholder, don't propagate backgroundColor/opaque to the editable textView. It is positioned over the placeholder to accept taps to begin editing, and if it's opaque/colored then it'll obscure the placeholder. - // The backgroundColor/opaque will be propagated to the editable textView when editing begins. - if (!_displayingPlaceholder) { - _textKitComponents.textView.opaque = opaque; - } - _placeholderTextKitComponents.textView.opaque = opaque; -} - -- (void)setLayerBacked:(BOOL)layerBacked -{ - ASDisplayNodeAssert(!layerBacked, @"Cannot set layerBacked to YES on ASEditableTextNode – instances must be view-backed in order to ensure touch events can be passed to the internal UITextView during editing."); - [super setLayerBacked:layerBacked]; -} - -- (BOOL)supportsLayerBacking -{ - return NO; -} - -#pragma mark - Configuration -@synthesize delegate = _delegate; - -- (void)setScrollEnabled:(BOOL)scrollEnabled -{ - AS::MutexLocker l(_textKitLock); - _scrollEnabled = scrollEnabled; - [_textKitComponents.textView setScrollEnabled:_scrollEnabled]; -} - -- (UITextView *)textView -{ - ASDisplayNodeAssertMainThread(); - [self view]; - ASDisplayNodeAssert(_textKitComponents.textView != nil, @"UITextView must be created in -[ASEditableTextNode didLoad]"); - return _textKitComponents.textView; -} - -- (void)setMaximumLinesToDisplay:(NSUInteger)maximumLines -{ - _maximumLinesToDisplay = maximumLines; - [self setNeedsLayout]; -} - -#pragma mark - -@dynamic typingAttributes; - -- (NSDictionary *)typingAttributes -{ - return _typingAttributes; -} - -- (void)setTypingAttributes:(NSDictionary *)typingAttributes -{ - if (ASObjectIsEqual(typingAttributes, _typingAttributes)) - return; - - _typingAttributes = [typingAttributes copy]; - - AS::MutexLocker l(_textKitLock); - - _textKitComponents.textView.typingAttributes = _typingAttributes; -} - -#pragma mark - -@dynamic selectedRange; - -- (NSRange)selectedRange -{ - AS::MutexLocker l(_textKitLock); - return _textKitComponents.textView.selectedRange; -} - -- (void)setSelectedRange:(NSRange)selectedRange -{ - AS::MutexLocker l(_textKitLock); - _textKitComponents.textView.selectedRange = selectedRange; -} - -- (CGRect)selectionRect { - UITextRange *range = [_textKitComponents.textView selectedTextRange]; - if (range != nil) { - return [_textKitComponents.textView firstRectForRange:range]; - } else { - return [_textKitComponents.textView bounds]; - } -} - -#pragma mark - Placeholder -- (BOOL)isDisplayingPlaceholder -{ - return _displayingPlaceholder; -} - -#pragma mark - -@dynamic attributedPlaceholderText; -- (NSAttributedString *)attributedPlaceholderText -{ - AS::MutexLocker l(_textKitLock); - - return [_placeholderTextKitComponents.textStorage copy]; -} - -- (void)setAttributedPlaceholderText:(NSAttributedString *)attributedPlaceholderText -{ - AS::MutexLocker l(_textKitLock); - - if (ASObjectIsEqual(_placeholderTextKitComponents.textStorage, attributedPlaceholderText)) - return; - - [_placeholderTextKitComponents.textStorage setAttributedString:attributedPlaceholderText ? : [[NSAttributedString alloc] initWithString:@""]]; - _textKitComponents.textView.accessibilityHint = attributedPlaceholderText.string; -} - -#pragma mark - Modifying User Text -@dynamic attributedText; -- (NSAttributedString *)attributedText -{ - // Per contract in our header, this value is nil when the placeholder is displayed. - if ([self isDisplayingPlaceholder]) - return nil; - - AS::MutexLocker l(_textKitLock); - - return [_textKitComponents.textStorage copy]; -} - -- (void)setAttributedText:(NSAttributedString *)attributedText -{ - AS::MutexLocker l(_textKitLock); - - // If we (_cmd) are called while the text view itself is updating (-textViewDidUpdate:), you cannot update the text storage and expect perfect propagation to the text view. - // Thus, we always update the textview directly if it's been created already. - if (ASObjectIsEqual((_textKitComponents.textView.attributedText ? : _textKitComponents.textStorage), attributedText)) - return; - - // If the cursor isn't at the end of the text, we need to preserve the selected range to avoid moving the cursor. - NSRange selectedRange = _textKitComponents.textView.selectedRange; - BOOL preserveSelectedRange = (selectedRange.location != _textKitComponents.textStorage.length); - - NSAttributedString *attributedStringToDisplay = nil; - - if (attributedText) - attributedStringToDisplay = attributedText; - // Otherwise, note that we don't simply nil out attributed text. Because the insertion point is guided by the attributes at index 0, we need to attribute an empty string to ensure the insert point obeys our typing attributes. - else - attributedStringToDisplay = [[NSAttributedString alloc] initWithString:@"" attributes:self.typingAttributes]; - - // Always prefer updating the text view directly if it's been created (see above). - if (_textKitComponents.textView) - [_textKitComponents.textView setAttributedText:attributedStringToDisplay]; - else - [_textKitComponents.textStorage setAttributedString:attributedStringToDisplay]; - - // Calculated size depends on the seeded text. - [self setNeedsLayout]; - - // Update if placeholder is shown. - [self _updateDisplayingPlaceholder]; - - // Preserve cursor range, if necessary. - if (preserveSelectedRange) { - _isPreservingSelection = YES; // Used in -textViewDidChangeSelection: to avoid informing our delegate about our preservation. - [_textKitComponents.textView setSelectedRange:selectedRange]; - _isPreservingSelection = NO; - } -} - -- (void)setInitialPrimaryLanguage:(NSString *)initialPrimaryLanguage { - _initialPrimaryLanguage = initialPrimaryLanguage; - ((ASPanningOverriddenUITextView *)_textKitComponents.textView).initialPrimaryLanguage = initialPrimaryLanguage; -} - -- (void)resetInitialPrimaryLanguage { - ((ASPanningOverriddenUITextView *)_textKitComponents.textView).initializedPrimaryInputLanguage = false; -} - -- (void)dropAutocorrection { - _isPreservingSelection = YES; // Used in -textViewDidChangeSelection: to avoid informing our delegate about our preservation. - _isPreservingText = YES; - - UITextView *textView = _textKitComponents.textView; - - NSRange rangeCopy = textView.selectedRange; - NSRange fakeRange = rangeCopy; - if (fakeRange.location != 0) { - fakeRange.location--; - } - [textView unmarkText]; - [textView setSelectedRange:fakeRange]; - [textView setSelectedRange:rangeCopy]; - - //[_textKitComponents.textView.inputDelegate textWillChange:_textKitComponents.textView]; - //[_textKitComponents.textView.inputDelegate textDidChange:_textKitComponents.textView]; - - _isPreservingSelection = NO; - _isPreservingText = NO; -} - -- (bool)isCurrentlyEmoji { - NSString *value = [[UITextInputMode currentInputMode] primaryLanguage]; - if ([value isEqualToString:@"emoji"]) { - return true; - } else { - return false; - } -} - -#pragma mark - Core -- (void)_updateDisplayingPlaceholder -{ - AS::MutexLocker l(_textKitLock); - - // Show the placeholder if necessary. - _displayingPlaceholder = (_textKitComponents.textStorage.length == 0); - _placeholderTextKitComponents.textView.hidden = !_displayingPlaceholder; - - // If hiding the placeholder, propagate backgroundColor/opaque to the editable textView. It is positioned over the placeholder to accept taps to begin editing, and was kept transparent so it doesn't obscure the placeholder text. Now that we're editing it and the placeholder is hidden, we can make it opaque to avoid unnecessary blending. - if (!_displayingPlaceholder) { - _textKitComponents.textView.opaque = self.isOpaque; - _textKitComponents.textView.backgroundColor = self.backgroundColor; - } else { - _textKitComponents.textView.opaque = NO; - _textKitComponents.textView.backgroundColor = nil; - } -} - -- (void)_layoutTextView -{ - AS::MutexLocker l(_textKitLock); - - // Layout filling our bounds. - _textKitComponents.textView.frame = self.bounds; - _placeholderTextKitComponents.textView.frame = self.bounds; - - // Note that both of these won't be necessary once we can disable scrolling, pending rdar://14729288 - // When we resize to fit (above) the prior layout becomes invalid. For whatever reason, UITextView doesn't invalidate its layout when its frame changes on its own, so we have to do so ourselves. - [_textKitComponents.layoutManager invalidateLayoutForCharacterRange:NSMakeRange(0, [_textKitComponents.textStorage length]) actualCharacterRange:NULL]; - - // When you type beyond UITextView's bounds it scrolls you down a line. We need to remain at the top. - [_textKitComponents.textView setContentOffset:CGPointZero animated:NO]; - [_textKitComponents.layoutManager ensureGlyphsForCharacterRange:NSMakeRange(0, [_textKitComponents.textStorage length])]; - NSRange range = [self selectedRange]; - range.location = range.location + range.length - 1; - range.length = 1; - [self.textView scrollRangeToVisible:range]; - - CGPoint bottomOffset = CGPointMake(0, self.textView.contentSize.height - self.textView.bounds.size.height); - //[self.textView setContentOffset:bottomOffset animated:NO]; -} - -#pragma mark - Keyboard -@dynamic textInputMode; -- (UITextInputMode *)textInputMode -{ - AS::MutexLocker l(_textKitLock); - return [_textKitComponents.textView textInputMode]; -} - -- (BOOL)isFirstResponder -{ - AS::MutexLocker l(_textKitLock); - return [_textKitComponents.textView isFirstResponder]; -} - -- (BOOL)canBecomeFirstResponder { - AS::MutexLocker l(_textKitLock); - return [_textKitComponents.textView canBecomeFirstResponder]; -} - -- (BOOL)becomeFirstResponder -{ - AS::MutexLocker l(_textKitLock); - return [_textKitComponents.textView becomeFirstResponder]; -} - -- (BOOL)canResignFirstResponder { - AS::MutexLocker l(_textKitLock); - return [_textKitComponents.textView canResignFirstResponder]; -} - -- (BOOL)resignFirstResponder -{ - AS::MutexLocker l(_textKitLock); - return [_textKitComponents.textView resignFirstResponder]; -} - -#pragma mark - UITextInputTraits - -- (_ASTextInputTraitsPendingState *)textInputTraits -{ - if (!_textInputTraits) { - _textInputTraits = [[_ASTextInputTraitsPendingState alloc] init]; - } - return _textInputTraits; -} - -- (void)setAutocapitalizationType:(UITextAutocapitalizationType)autocapitalizationType -{ - AS::MutexLocker l(_textInputTraitsLock); - if (self.isNodeLoaded) { - [self.textView setAutocapitalizationType:autocapitalizationType]; - } else { - [self.textInputTraits setAutocapitalizationType:autocapitalizationType]; - } -} - -- (UITextAutocapitalizationType)autocapitalizationType -{ - AS::MutexLocker l(_textInputTraitsLock); - if (self.isNodeLoaded) { - return [self.textView autocapitalizationType]; - } else { - return [self.textInputTraits autocapitalizationType]; - } -} - -- (void)setAutocorrectionType:(UITextAutocorrectionType)autocorrectionType -{ - AS::MutexLocker l(_textInputTraitsLock); - if (self.isNodeLoaded) { - [self.textView setAutocorrectionType:autocorrectionType]; - } else { - [self.textInputTraits setAutocorrectionType:autocorrectionType]; - } -} - -- (UITextAutocorrectionType)autocorrectionType -{ - AS::MutexLocker l(_textInputTraitsLock); - if (self.isNodeLoaded) { - return [self.textView autocorrectionType]; - } else { - return [self.textInputTraits autocorrectionType]; - } -} - -- (void)setSpellCheckingType:(UITextSpellCheckingType)spellCheckingType -{ - AS::MutexLocker l(_textInputTraitsLock); - if (self.isNodeLoaded) { - [self.textView setSpellCheckingType:spellCheckingType]; - } else { - [self.textInputTraits setSpellCheckingType:spellCheckingType]; - } -} - -- (UITextSpellCheckingType)spellCheckingType -{ - AS::MutexLocker l(_textInputTraitsLock); - if (self.isNodeLoaded) { - return [self.textView spellCheckingType]; - } else { - return [self.textInputTraits spellCheckingType]; - } -} - -- (void)setEnablesReturnKeyAutomatically:(BOOL)enablesReturnKeyAutomatically -{ - AS::MutexLocker l(_textInputTraitsLock); - if (self.isNodeLoaded) { - [self.textView setEnablesReturnKeyAutomatically:enablesReturnKeyAutomatically]; - } else { - [self.textInputTraits setEnablesReturnKeyAutomatically:enablesReturnKeyAutomatically]; - } -} - -- (BOOL)enablesReturnKeyAutomatically -{ - AS::MutexLocker l(_textInputTraitsLock); - if (self.isNodeLoaded) { - return [self.textView enablesReturnKeyAutomatically]; - } else { - return [self.textInputTraits enablesReturnKeyAutomatically]; - } -} - -- (void)setKeyboardAppearance:(UIKeyboardAppearance)setKeyboardAppearance -{ - AS::MutexLocker l(_textInputTraitsLock); - if (self.isNodeLoaded) { - [self.textView setKeyboardAppearance:setKeyboardAppearance]; - } else { - [self.textInputTraits setKeyboardAppearance:setKeyboardAppearance]; - } -} - -- (UIKeyboardAppearance)keyboardAppearance -{ - AS::MutexLocker l(_textInputTraitsLock); - if (self.isNodeLoaded) { - return [self.textView keyboardAppearance]; - } else { - return [self.textInputTraits keyboardAppearance]; - } -} - -- (void)setKeyboardType:(UIKeyboardType)keyboardType -{ - AS::MutexLocker l(_textInputTraitsLock); - if (self.isNodeLoaded) { - [self.textView setKeyboardType:keyboardType]; - } else { - [self.textInputTraits setKeyboardType:keyboardType]; - } -} - -- (UIKeyboardType)keyboardType -{ - AS::MutexLocker l(_textInputTraitsLock); - if (self.isNodeLoaded) { - return [self.textView keyboardType]; - } else { - return [self.textInputTraits keyboardType]; - } -} - -- (void)setReturnKeyType:(UIReturnKeyType)returnKeyType -{ - AS::MutexLocker l(_textInputTraitsLock); - if (self.isNodeLoaded) { - [self.textView setReturnKeyType:returnKeyType]; - } else { - [self.textInputTraits setReturnKeyType:returnKeyType]; - } -} - -- (UIReturnKeyType)returnKeyType -{ - AS::MutexLocker l(_textInputTraitsLock); - if (self.isNodeLoaded) { - return [self.textView returnKeyType]; - } else { - return [self.textInputTraits returnKeyType]; - } -} - -- (void)setSecureTextEntry:(BOOL)secureTextEntry -{ - AS::MutexLocker l(_textInputTraitsLock); - if (self.isNodeLoaded) { - [self.textView setSecureTextEntry:secureTextEntry]; - } else { - [self.textInputTraits setSecureTextEntry:secureTextEntry]; - } -} - -- (BOOL)isSecureTextEntry -{ - AS::MutexLocker l(_textInputTraitsLock); - if (self.isNodeLoaded) { - return [self.textView isSecureTextEntry]; - } else { - return [self.textInputTraits isSecureTextEntry]; - } -} - -#pragma mark - UITextView Delegate -- (BOOL)textViewShouldBeginEditing:(UITextView *)textView -{ - // Delegateify. - return [self _delegateShouldBeginEditing]; -} - -- (void)textViewDidBeginEditing:(UITextView *)textView -{ - // Delegateify. - [self _delegateDidBeginEditing]; -} - -- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text -{ - if (_isPreservingText) { - return false; - } - // Delegateify. - return [self _delegateShouldChangeTextInRange:range replacementText:text]; -} - -- (void)textViewDidChange:(UITextView *)textView -{ - AS::MutexLocker l(_textKitLock); - - // Note we received a text changed event. - // This is used by _delegateDidChangeSelectionFromSelectedRange:toSelectedRange: to distinguish between selection changes that happen because of editing or pure selection changes. - _selectionChangedForEditedText = YES; - - // Update if the placeholder is visible. - [self _updateDisplayingPlaceholder]; - - // Invalidate, as our calculated size depends on the textview's seeded text. - [self invalidateCalculatedLayout]; - - // Delegateify. - [self _delegateDidUpdateText]; -} - -- (void)textViewDidChangeSelection:(UITextView *)textView -{ - // Typing attributes get reset when selection changes. Reapply them so they actually obey our header. - _textKitComponents.textView.typingAttributes = _typingAttributes; - - // If we're only changing selection to preserve it, don't notify about anything. - if (_isPreservingSelection) - return; - - // Note if we receive a -textDidChange: between now and when we delegatify. - // This is used by _delegateDidChangeSelectionFromSelectedRange:toSelectedRange: to distinguish between selection changes that happen because of editing or pure selection changes. - _selectionChangedForEditedText = NO; - - NSRange fromSelectedRange = _previousSelectedRange; - NSRange toSelectedRange = self.selectedRange; - _previousSelectedRange = toSelectedRange; - - // Delegateify. - [self _delegateDidChangeSelectionFromSelectedRange:fromSelectedRange toSelectedRange:toSelectedRange]; -} - -- (void)textViewDidEndEditing:(UITextView *)textView -{ - // Delegateify. - [self _delegateDidFinishEditing]; -} - -#pragma mark - NSLayoutManager Delegate - -- (NSUInteger)layoutManager:(NSLayoutManager *)layoutManager shouldGenerateGlyphs:(const CGGlyph *)glyphs properties:(const NSGlyphProperty *)properties characterIndexes:(const NSUInteger *)characterIndexes font:(UIFont *)aFont forGlyphRange:(NSRange)glyphRange -{ - return [_wordKerner layoutManager:layoutManager shouldGenerateGlyphs:glyphs properties:properties characterIndexes:characterIndexes font:aFont forGlyphRange:glyphRange]; -} - -- (NSControlCharacterAction)layoutManager:(NSLayoutManager *)layoutManager shouldUseAction:(NSControlCharacterAction)defaultAction forControlCharacterAtIndex:(NSUInteger)characterIndex -{ - return [_wordKerner layoutManager:layoutManager shouldUseAction:defaultAction forControlCharacterAtIndex:characterIndex]; -} - -- (CGRect)layoutManager:(NSLayoutManager *)layoutManager boundingBoxForControlGlyphAtIndex:(NSUInteger)glyphIndex forTextContainer:(NSTextContainer *)textContainer proposedLineFragment:(CGRect)proposedRect glyphPosition:(CGPoint)glyphPosition characterIndex:(NSUInteger)characterIndex -{ - return [_wordKerner layoutManager:layoutManager boundingBoxForControlGlyphAtIndex:glyphIndex forTextContainer:textContainer proposedLineFragment:proposedRect glyphPosition:glyphPosition characterIndex:characterIndex]; -} - -- (BOOL)layoutManager:(NSLayoutManager *)layoutManager shouldSetLineFragmentRect:(inout CGRect *)lineFragmentRect lineFragmentUsedRect:(inout CGRect *)lineFragmentUsedRect baselineOffset:(inout CGFloat *)baselineOffset inTextContainer:(NSTextContainer *)textContainer forGlyphRange:(NSRange)glyphRange { - CGFloat fontLineHeight; - UIFont *baseFont = _baseFont; - if (_typingAttributes[NSFontAttributeName] != nil) { - baseFont = _typingAttributes[NSFontAttributeName]; - } - if (baseFont == nil) { - fontLineHeight = 20.0; - } else { - CGFloat fontAscent = baseFont.ascender; - CGFloat fontDescent = ABS(baseFont.descender); - fontLineHeight = floor(fontAscent + fontDescent); - } - CGFloat lineHeight = fontLineHeight * 1.0; - CGFloat baselineNudge = (lineHeight - fontLineHeight) * 0.6f; - - CGRect rect = *lineFragmentRect; - rect.size.height = lineHeight; - - CGRect usedRect = *lineFragmentUsedRect; - usedRect.size.height = MAX(lineHeight, usedRect.size.height); - - *lineFragmentRect = rect; - *lineFragmentUsedRect = usedRect; - *baselineOffset = *baselineOffset + baselineNudge; - - return true; -} - -#pragma mark - Geometry -- (CGRect)frameForTextRange:(NSRange)textRange -{ - AS::MutexLocker l(_textKitLock); - - // Bail on invalid range. - if (NSMaxRange(textRange) > [_textKitComponents.textStorage length]) { - ASDisplayNodeAssert(NO, @"Invalid range"); - return CGRectZero; - } - - // Force glyph generation and layout. - [_textKitComponents.layoutManager ensureLayoutForTextContainer:_textKitComponents.textContainer]; - - NSRange glyphRange = [_textKitComponents.layoutManager glyphRangeForCharacterRange:textRange actualCharacterRange:NULL]; - CGRect textRect = [_textKitComponents.layoutManager boundingRectForGlyphRange:glyphRange inTextContainer:_textKitComponents.textContainer]; - return [_textKitComponents.textView convertRect:textRect toView:self.view]; -} - -#pragma mark - -- (BOOL)_delegateShouldBeginEditing -{ - if ([_delegate respondsToSelector:@selector(editableTextNodeShouldBeginEditing:)]) { - return [_delegate editableTextNodeShouldBeginEditing:self]; - } - return YES; -} - -- (void)_delegateDidBeginEditing -{ - if ([_delegate respondsToSelector:@selector(editableTextNodeDidBeginEditing:)]) - [_delegate editableTextNodeDidBeginEditing:self]; -} - -- (BOOL)_delegateShouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text -{ - if ([_delegate respondsToSelector:@selector(editableTextNode:shouldChangeTextInRange:replacementText:)]) { - return [_delegate editableTextNode:self shouldChangeTextInRange:range replacementText:text]; - } - - return YES; -} - -- (void)_delegateDidChangeSelectionFromSelectedRange:(NSRange)fromSelectedRange toSelectedRange:(NSRange)toSelectedRange -{ - // There are two reasons we're invoking the delegate on the next run of the runloop. - // 1. UITextView invokes its delegate methods when it's in the middle of text-processing. For example, -textViewDidChange: is invoked before you can truly rely on the changes being propagated throughout the Text Kit hierarchy. - // 2. This delegate method (-textViewDidChangeSelection:) is called both before -textViewDidChange: and before the layout manager/etc. has necessarily generated+laid out its glyphs. Because of the former, we need to wait until -textViewDidChange: has had an opportunity to be called so can accurately determine whether this selection change is due to editing (_selectionChangedForEditedText). - // Thus, to avoid calling out to client code in the middle of UITextView's processing, we call the delegate on the next run of the runloop, when all such internal processing is surely done. - dispatch_async(dispatch_get_main_queue(), ^{ - if ([_delegate respondsToSelector:@selector(editableTextNodeDidChangeSelection:fromSelectedRange:toSelectedRange:dueToEditing:)]) - [_delegate editableTextNodeDidChangeSelection:self fromSelectedRange:fromSelectedRange toSelectedRange:toSelectedRange dueToEditing:_selectionChangedForEditedText]; - }); -} - -- (void)_delegateDidUpdateText -{ - // Note that because -editableTextNodeDidUpdateText: passes no state, the current state of the receiver will be accessed. Thus, it's not useful to enqueue a second delegation call if the first hasn't happened yet -- doing so will result in the delegate receiving -editableTextNodeDidUpdateText: when the "updated text" has already been processed. This may sound innocuous, but because our delegation may cause additional updates to the textview's string, and because such updates discard spelling suggestions and autocompletions (like double-space to `.`), it can actually be quite dangerous! - if (_delegateDidUpdateEnqueued) - return; - - _delegateDidUpdateEnqueued = YES; - - // UITextView invokes its delegate methods when it's in the middle of text-processing. For example, -textViewDidChange: is invoked before you can truly rely on the changes being propagated throughout the Text Kit hierarchy. - // Thus, to avoid calling out to client code in the middle of UITextView's processing, we call the delegate on the next run of the runloop, when all such internal processing is surely done. - dispatch_async(dispatch_get_main_queue(), ^{ - _delegateDidUpdateEnqueued = NO; - if ([_delegate respondsToSelector:@selector(editableTextNodeDidUpdateText:)]) - [_delegate editableTextNodeDidUpdateText:self]; - }); -} - -- (void)_delegateDidFinishEditing -{ - if ([_delegate respondsToSelector:@selector(editableTextNodeDidFinishEditing:)]) - [_delegate editableTextNodeDidFinishEditing:self]; -} - -#pragma mark - UIAccessibilityContainer - -- (NSInteger)accessibilityElementCount -{ - if (!self.isNodeLoaded) { - ASDisplayNodeFailAssert(@"Cannot access accessibilityElementCount since ASEditableTextNode is not loaded"); - return 0; - } - return 1; -} - -- (NSArray *)accessibilityElements -{ - if (!self.isNodeLoaded) { - ASDisplayNodeFailAssert(@"Cannot access accessibilityElements since ASEditableTextNode is not loaded"); - return @[]; - } - return @[self.textView]; -} - -- (id)accessibilityElementAtIndex:(NSInteger)index -{ - if (!self.isNodeLoaded) { - ASDisplayNodeFailAssert(@"Cannot access accessibilityElementAtIndex: since ASEditableTextNode is not loaded"); - return nil; - } - return self.textView; -} - -- (NSInteger)indexOfAccessibilityElement:(id)element -{ - return 0; -} - -@end diff --git a/submodules/AsyncDisplayKit/Source/ASExperimentalFeatures.mm b/submodules/AsyncDisplayKit/Source/ASExperimentalFeatures.mm deleted file mode 100644 index 653db2c370..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASExperimentalFeatures.mm +++ /dev/null @@ -1,52 +0,0 @@ -// -// ASExperimentalFeatures.mm -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#import - -NSArray *ASExperimentalFeaturesGetNames(ASExperimentalFeatures flags) -{ - NSArray *allNames = ASCreateOnce((@[@"exp_graphics_contexts", - @"exp_text_node", - @"exp_interface_state_coalesce", - @"exp_unfair_lock", - @"exp_infer_layer_defaults", - @"exp_collection_teardown", - @"exp_framesetter_cache", - @"exp_skip_clear_data", - @"exp_did_enter_preload_skip_asm_layout", - @"exp_disable_a11y_cache", - @"exp_dispatch_apply", - @"exp_image_downloader_priority", - @"exp_text_drawing"])); - if (flags == ASExperimentalFeatureAll) { - return allNames; - } - - // Go through all names, testing each bit. - NSUInteger i = 0; - return ASArrayByFlatMapping(allNames, NSString *name, ({ - (flags & (1 << i++)) ? name : nil; - })); -} - -// O(N^2) but with counts this small, it's probably faster -// than hashing the strings. -ASExperimentalFeatures ASExperimentalFeaturesFromArray(NSArray *array) -{ - NSArray *allNames = ASExperimentalFeaturesGetNames(ASExperimentalFeatureAll); - ASExperimentalFeatures result = 0; - for (NSString *str in array) { - NSUInteger i = [allNames indexOfObject:str]; - if (i != NSNotFound) { - result |= (1 << i); - } - } - return result; -} diff --git a/submodules/AsyncDisplayKit/Source/ASGraphicsContext.mm b/submodules/AsyncDisplayKit/Source/ASGraphicsContext.mm deleted file mode 100644 index b181ee2728..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASGraphicsContext.mm +++ /dev/null @@ -1,167 +0,0 @@ -// -// ASGraphicsContext.mm -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import "ASCGImageBuffer.h" -#import -#import -#import -#import -#import -#import - -/** - * Our version of the private CGBitmapGetAlignedBytesPerRow function. - * - * In both 32-bit and 64-bit, this function rounds up to nearest multiple of 32 - * in iOS 9, 10, and 11. We'll try to catch if this ever changes by asserting that - * the bytes-per-row for a 1x1 context from the system is 32. - */ -static size_t ASGraphicsGetAlignedBytesPerRow(size_t baseValue) { - // Add 31 then zero out low 5 bits. - return (baseValue + 31) & ~0x1F; -} - -/** - * A key used to associate CGContextRef -> NSMutableData, nonatomic retain. - * - * That way the data will be released when the context dies. If they pull an image, - * we will retain the data object (in a CGDataProvider) before releasing the context. - */ -static UInt8 __contextDataAssociationKey; - -#pragma mark - Graphics Contexts - -void ASGraphicsBeginImageContextWithOptions(CGSize size, BOOL opaque, CGFloat scale) -{ - if (!ASActivateExperimentalFeature(ASExperimentalGraphicsContexts)) { - UIGraphicsBeginImageContextWithOptions(size, opaque, scale); - return; - } - - // We use "reference contexts" to get device-specific options that UIKit - // uses. - static dispatch_once_t onceToken; - static CGContextRef refCtxOpaque; - static CGContextRef refCtxTransparent; - dispatch_once(&onceToken, ^{ - UIGraphicsBeginImageContextWithOptions(CGSizeMake(1, 1), YES, 1); - refCtxOpaque = CGContextRetain(UIGraphicsGetCurrentContext()); - ASDisplayNodeCAssert(CGBitmapContextGetBytesPerRow(refCtxOpaque) == 32, @"Expected bytes per row to be aligned to 32. Has CGBitmapGetAlignedBytesPerRow implementation changed?"); - UIGraphicsEndImageContext(); - - // Make transparent ref context. - UIGraphicsBeginImageContextWithOptions(CGSizeMake(1, 1), NO, 1); - refCtxTransparent = CGContextRetain(UIGraphicsGetCurrentContext()); - UIGraphicsEndImageContext(); - }); - - // These options are taken from UIGraphicsBeginImageContext. - CGContextRef refCtx = opaque ? refCtxOpaque : refCtxTransparent; - CGBitmapInfo bitmapInfo = CGBitmapContextGetBitmapInfo(refCtx); - - if (scale == 0) { - scale = ASScreenScale(); - } - size_t intWidth = (size_t)ceil(size.width * scale); - size_t intHeight = (size_t)ceil(size.height * scale); - size_t bitsPerComponent = CGBitmapContextGetBitsPerComponent(refCtx); - size_t bytesPerRow = CGBitmapContextGetBitsPerPixel(refCtx) * intWidth / 8; - bytesPerRow = ASGraphicsGetAlignedBytesPerRow(bytesPerRow); - size_t bufferSize = bytesPerRow * intHeight; - CGColorSpaceRef colorspace = CGBitmapContextGetColorSpace(refCtx); - - // We create our own buffer, and wrap the context around that. This way we can prevent - // the copy that usually gets made when you form a CGImage from the context. - ASCGImageBuffer *buffer = [[ASCGImageBuffer alloc] initWithLength:bufferSize]; - - CGContextRef context = CGBitmapContextCreate(buffer.mutableBytes, intWidth, intHeight, bitsPerComponent, bytesPerRow, colorspace, bitmapInfo); - - // Transfer ownership of the data to the context. So that if the context - // is destroyed before we create an image from it, the data will be released. - objc_setAssociatedObject((__bridge id)context, &__contextDataAssociationKey, buffer, OBJC_ASSOCIATION_RETAIN_NONATOMIC); - - // Set the CTM to account for iOS orientation & specified scale. - // If only we could use CGContextSetBaseCTM. It doesn't - // seem like there are any consequences for our use case - // but we'll be on the look out. The internet hinted that it - // affects shadowing but I tested and shadowing works. - CGContextTranslateCTM(context, 0, intHeight); - CGContextScaleCTM(context, scale, -scale); - - // Save the state so we can restore it and recover our scale in GetImageAndEnd - CGContextSaveGState(context); - - // Transfer context ownership to the UIKit stack. - UIGraphicsPushContext(context); - CGContextRelease(context); -} - -UIImage * _Nullable ASGraphicsGetImageAndEndCurrentContext() NS_RETURNS_RETAINED -{ - if (!ASActivateExperimentalFeature(ASExperimentalGraphicsContexts)) { - UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); - return image; - } - - // Pop the context and make sure we have one. - CGContextRef context = UIGraphicsGetCurrentContext(); - if (context == NULL) { - ASDisplayNodeCFailAssert(@"Can't end image context without having begun one."); - return nil; - } - - // Read the device-specific ICC-based color space to use for the image. - // For DeviceRGB contexts (e.g. UIGraphics), CGBitmapContextCreateImage - // generates an image in a device-specific color space (for wide color support). - // We replicate that behavior, even though at this time CA does not - // require the image to be in this space. Plain DeviceRGB images seem - // to be treated exactly the same, but better safe than sorry. - static CGColorSpaceRef imageColorSpace; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - UIGraphicsBeginImageContextWithOptions(CGSizeMake(1, 1), YES, 0); - UIImage *refImage = UIGraphicsGetImageFromCurrentImageContext(); - imageColorSpace = CGColorSpaceRetain(CGImageGetColorSpace(refImage.CGImage)); - ASDisplayNodeCAssertNotNil(imageColorSpace, nil); - UIGraphicsEndImageContext(); - }); - - // Retrieve our buffer and create a CGDataProvider from it. - ASCGImageBuffer *buffer = objc_getAssociatedObject((__bridge id)context, &__contextDataAssociationKey); - ASDisplayNodeCAssertNotNil(buffer, nil); - CGDataProviderRef provider = [buffer createDataProviderAndInvalidate]; - - // Create the CGImage. Options taken from CGBitmapContextCreateImage. - CGImageRef cgImg = CGImageCreate(CGBitmapContextGetWidth(context), CGBitmapContextGetHeight(context), CGBitmapContextGetBitsPerComponent(context), CGBitmapContextGetBitsPerPixel(context), CGBitmapContextGetBytesPerRow(context), imageColorSpace, CGBitmapContextGetBitmapInfo(context), provider, NULL, true, kCGRenderingIntentDefault); - CGDataProviderRelease(provider); - - // We saved our GState right after setting the CTM so that we could restore it - // here and get the original scale back. - CGContextRestoreGState(context); - CGFloat scale = CGContextGetCTM(context).a; - - // Note: popping from the UIKit stack will probably destroy the context. - context = NULL; - UIGraphicsPopContext(); - - UIImage *result = [[UIImage alloc] initWithCGImage:cgImg scale:scale orientation:UIImageOrientationUp]; - CGImageRelease(cgImg); - return result; -} - -void ASGraphicsEndImageContext() -{ - if (!ASActivateExperimentalFeature(ASExperimentalGraphicsContexts)) { - UIGraphicsEndImageContext(); - return; - } - - UIGraphicsPopContext(); -} diff --git a/submodules/AsyncDisplayKit/Source/ASHashing.mm b/submodules/AsyncDisplayKit/Source/ASHashing.mm deleted file mode 100644 index 17bf66bd82..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASHashing.mm +++ /dev/null @@ -1,38 +0,0 @@ -// -// ASHashing.mm -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#define ELF_STEP(B) T1 = (H << 4) + B; T2 = T1 & 0xF0000000; if (T2) T1 ^= (T2 >> 24); T1 &= (~T2); H = T1; - -/** - * The hashing algorithm copied from CoreFoundation CFHashBytes function. - * https://opensource.apple.com/source/CF/CF-1153.18/CFUtilities.c.auto.html - */ -NSUInteger ASHashBytes(void *bytesarg, size_t length) { - /* The ELF hash algorithm, used in the ELF object file format */ - uint8_t *bytes = (uint8_t *)bytesarg; - UInt32 H = 0, T1, T2; - SInt32 rem = (SInt32)length; - while (3 < rem) { - ELF_STEP(bytes[length - rem]); - ELF_STEP(bytes[length - rem + 1]); - ELF_STEP(bytes[length - rem + 2]); - ELF_STEP(bytes[length - rem + 3]); - rem -= 4; - } - switch (rem) { - case 3: ELF_STEP(bytes[length - 3]); - case 2: ELF_STEP(bytes[length - 2]); - case 1: ELF_STEP(bytes[length - 1]); - case 0: ; - } - return H; -} - -#undef ELF_STEP diff --git a/submodules/AsyncDisplayKit/Source/ASInternalHelpers.mm b/submodules/AsyncDisplayKit/Source/ASInternalHelpers.mm deleted file mode 100644 index a9926ccca4..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASInternalHelpers.mm +++ /dev/null @@ -1,233 +0,0 @@ -// -// ASInternalHelpers.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#import - -#import -#import - -#import -#import -#import - -static NSNumber *allowsGroupOpacityFromUIKitOrNil; -static NSNumber *allowsEdgeAntialiasingFromUIKitOrNil; - -BOOL ASDefaultAllowsGroupOpacity() -{ - static BOOL groupOpacity; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - NSNumber *groupOpacityObj = allowsGroupOpacityFromUIKitOrNil ?: [NSBundle.mainBundle objectForInfoDictionaryKey:@"UIViewGroupOpacity"]; - groupOpacity = groupOpacityObj ? groupOpacityObj.boolValue : YES; - }); - return groupOpacity; -} - -BOOL ASDefaultAllowsEdgeAntialiasing() -{ - static BOOL edgeAntialiasing; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - NSNumber *antialiasingObj = allowsEdgeAntialiasingFromUIKitOrNil ?: [NSBundle.mainBundle objectForInfoDictionaryKey:@"UIViewEdgeAntialiasing"]; - edgeAntialiasing = antialiasingObj ? antialiasingObj.boolValue : NO; - }); - return edgeAntialiasing; -} - -void ASInitializeFrameworkMainThread(void) -{ - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - ASDisplayNodeCAssertMainThread(); - // Ensure these values are cached on the main thread before needed in the background. - if (ASActivateExperimentalFeature(ASExperimentalLayerDefaults)) { - // Nop. We will gather default values on-demand in ASDefaultAllowsGroupOpacity and ASDefaultAllowsEdgeAntialiasing - } else { - CALayer *layer = [[[UIView alloc] init] layer]; - allowsGroupOpacityFromUIKitOrNil = @(layer.allowsGroupOpacity); - allowsEdgeAntialiasingFromUIKitOrNil = @(layer.allowsEdgeAntialiasing); - } - ASNotifyInitialized(); - }); -} - -BOOL ASSubclassOverridesSelector(Class superclass, Class subclass, SEL selector) -{ - if (superclass == subclass) return NO; // Even if the class implements the selector, it doesn't override itself. - Method superclassMethod = class_getInstanceMethod(superclass, selector); - Method subclassMethod = class_getInstanceMethod(subclass, selector); - return (superclassMethod != subclassMethod); -} - -BOOL ASSubclassOverridesClassSelector(Class superclass, Class subclass, SEL selector) -{ - if (superclass == subclass) return NO; // Even if the class implements the selector, it doesn't override itself. - Method superclassMethod = class_getClassMethod(superclass, selector); - Method subclassMethod = class_getClassMethod(subclass, selector); - return (superclassMethod != subclassMethod); -} - -IMP ASReplaceMethodWithBlock(Class c, SEL origSEL, id block) -{ - NSCParameterAssert(block); - - // Get original method - Method origMethod = class_getInstanceMethod(c, origSEL); - NSCParameterAssert(origMethod); - - // Convert block to IMP trampoline and replace method implementation - IMP newIMP = imp_implementationWithBlock(block); - - // Try adding the method if not yet in the current class - if (!class_addMethod(c, origSEL, newIMP, method_getTypeEncoding(origMethod))) { - return method_setImplementation(origMethod, newIMP); - } else { - return method_getImplementation(origMethod); - } -} - -void ASPerformBlockOnMainThread(void (^block)(void)) -{ - if (block == nil){ - return; - } - if (ASDisplayNodeThreadIsMain()) { - block(); - } else { - dispatch_async(dispatch_get_main_queue(), block); - } -} - -void ASPerformBlockOnBackgroundThread(void (^block)(void)) -{ - if (block == nil){ - return; - } - if (ASDisplayNodeThreadIsMain()) { - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), block); - } else { - block(); - } -} - -void ASPerformBackgroundDeallocation(id __strong _Nullable * _Nonnull object) -{ - [[ASDeallocQueue sharedDeallocationQueue] releaseObjectInBackground:object]; -} - -Class _Nullable ASGetClassFromType(const char * _Nullable type) -{ - // Class types all start with @" - if (type == NULL || strncmp(type, "@\"", 2) != 0) { - return Nil; - } - - // Ensure length >= 3 - size_t typeLength = strlen(type); - if (typeLength < 3) { - ASDisplayNodeCFailAssert(@"Got invalid type-encoding: %s", type); - return Nil; - } - - // Copy type[2..(end-1)]. So @"UIImage" -> UIImage - size_t resultLength = typeLength - 3; - char className[resultLength + 1]; - strncpy(className, type + 2, resultLength); - className[resultLength] = '\0'; - return objc_getClass(className); -} - -CGFloat ASScreenScale() -{ - static CGFloat __scale = 0.0; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - UIGraphicsBeginImageContextWithOptions(CGSizeMake(1, 1), YES, 0); - __scale = CGContextGetCTM(UIGraphicsGetCurrentContext()).a; - UIGraphicsEndImageContext(); - }); - return __scale; -} - -CGSize ASFloorSizeValues(CGSize s) -{ - return CGSizeMake(ASFloorPixelValue(s.width), ASFloorPixelValue(s.height)); -} - -// See ASCeilPixelValue for a more thoroguh explanation of (f + FLT_EPSILON), -// but here is some quick math: -// -// Imagine a layout that comes back with a height of 100.66666666663 -// for a 3x deice: -// 100.66666666663 * 3 = 301.99999999988995 -// floor(301.99999999988995) = 301 -// 301 / 3 = 100.333333333 -// -// If we add FLT_EPSILON to normalize the garbage at the end we get: -// po (100.66666666663 + FLT_EPSILON) * 3 = 302.00000035751782 -// floor(302.00000035751782) = 302 -// 302/3 = 100.66666666 -CGFloat ASFloorPixelValue(CGFloat f) -{ - CGFloat scale = ASScreenScale(); - return floor((f + FLT_EPSILON) * scale) / scale; -} - -CGPoint ASCeilPointValues(CGPoint p) -{ - return CGPointMake(ASCeilPixelValue(p.x), ASCeilPixelValue(p.y)); -} - -CGSize ASCeilSizeValues(CGSize s) -{ - return CGSizeMake(ASCeilPixelValue(s.width), ASCeilPixelValue(s.height)); -} - -// With 3x devices layouts will often to compute to pixel bounds but -// include garbage values beyond the precision of a float/double. -// This garbage can result in a pixel value being rounded up when it isn't -// necessary. -// -// For example, imagine a layout that comes back with a height of 100.666666666669 -// for a 3x device: -// 100.666666666669 * 3 = 302.00000000000699 -// ceil(302.00000000000699) = 303 -// 303/3 = 101 -// -// If we use FLT_EPSILON to get rid of the garbage at the end of the value, -// things work as expected: -// (100.666666666669 - FLT_EPSILON) * 3 = 301.99999964237912 -// ceil(301.99999964237912) = 302 -// 302/3 = 100.666666666 -// -// For even more conversation around this, see: -// https://github.com/TextureGroup/Texture/issues/838 -CGFloat ASCeilPixelValue(CGFloat f) -{ - CGFloat scale = ASScreenScale(); - return ceil((f - FLT_EPSILON) * scale) / scale; -} - -CGFloat ASRoundPixelValue(CGFloat f) -{ - CGFloat scale = ASScreenScale(); - return round(f * scale) / scale; -} - -@implementation NSIndexPath (ASInverseComparison) - -- (NSComparisonResult)asdk_inverseCompare:(NSIndexPath *)otherIndexPath -{ - return [otherIndexPath compare:self]; -} - -@end diff --git a/submodules/AsyncDisplayKit/Source/ASLayout.mm b/submodules/AsyncDisplayKit/Source/ASLayout.mm deleted file mode 100644 index 6a6a96f24c..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASLayout.mm +++ /dev/null @@ -1,378 +0,0 @@ -// -// ASLayout.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#import -#import - -#import -#import -#import "ASLayoutSpecUtilities.h" -#import "ASLayoutSpec+Subclasses.h" - -#import -#import -#import - -NSString *const ASThreadDictMaxConstraintSizeKey = @"kASThreadDictMaxConstraintSizeKey"; - -CGPoint const ASPointNull = {NAN, NAN}; - -BOOL ASPointIsNull(CGPoint point) -{ - return isnan(point.x) && isnan(point.y); -} - -/** - * Creates an defined number of " |" indent blocks for the recursive description. - */ -ASDISPLAYNODE_INLINE AS_WARN_UNUSED_RESULT NSString * descriptionIndents(NSUInteger indents) -{ - NSMutableString *description = [NSMutableString string]; - for (NSUInteger i = 0; i < indents; i++) { - [description appendString:@" |"]; - } - if (indents > 0) { - [description appendString:@" "]; - } - return description; -} - -ASDISPLAYNODE_INLINE AS_WARN_UNUSED_RESULT BOOL ASLayoutIsDisplayNodeType(ASLayout *layout) -{ - return layout.type == ASLayoutElementTypeDisplayNode; -} - -@interface ASLayout () -{ - ASLayoutElementType _layoutElementType; - std::atomic_bool _retainSublayoutElements; -} -@end - -@implementation ASLayout - -@dynamic frame, type; - -static std::atomic_bool static_retainsSublayoutLayoutElements = ATOMIC_VAR_INIT(NO); - -+ (void)setShouldRetainSublayoutLayoutElements:(BOOL)shouldRetain -{ - static_retainsSublayoutLayoutElements.store(shouldRetain); -} - -+ (BOOL)shouldRetainSublayoutLayoutElements -{ - return static_retainsSublayoutLayoutElements.load(); -} - -- (instancetype)initWithLayoutElement:(id)layoutElement - size:(CGSize)size - position:(CGPoint)position - sublayouts:(nullable NSArray *)sublayouts -{ - NSParameterAssert(layoutElement); - - self = [super init]; - if (self) { -#if ASDISPLAYNODE_ASSERTIONS_ENABLED - for (ASLayout *sublayout in sublayouts) { - ASDisplayNodeAssert(ASPointIsNull(sublayout.position) == NO, @"Invalid position is not allowed in sublayout."); - } -#endif - - _layoutElement = layoutElement; - - // Read this now to avoid @c weak overhead later. - _layoutElementType = layoutElement.layoutElementType; - - if (!ASIsCGSizeValidForSize(size)) { - //ASDisplayNodeFailAssert(@"layoutSize is invalid and unsafe to provide to Core Animation! Release configurations will force to 0, 0. Size = %@, node = %@", NSStringFromCGSize(size), layoutElement); - size = CGSizeZero; - } else { - size = CGSizeMake(ASCeilPixelValue(size.width), ASCeilPixelValue(size.height)); - } - _size = size; - - if (ASPointIsNull(position) == NO) { - _position = ASCeilPointValues(position); - } else { - _position = position; - } - - _sublayouts = [sublayouts copy] ?: @[]; - - if ([ASLayout shouldRetainSublayoutLayoutElements]) { - [self retainSublayoutElements]; - } - } - - return self; -} - -#pragma mark - Class Constructors - -+ (instancetype)layoutWithLayoutElement:(id)layoutElement - size:(CGSize)size - position:(CGPoint)position - sublayouts:(nullable NSArray *)sublayouts NS_RETURNS_RETAINED -{ - return [[self alloc] initWithLayoutElement:layoutElement - size:size - position:position - sublayouts:sublayouts]; -} - -+ (instancetype)layoutWithLayoutElement:(id)layoutElement - size:(CGSize)size - sublayouts:(nullable NSArray *)sublayouts NS_RETURNS_RETAINED -{ - return [self layoutWithLayoutElement:layoutElement - size:size - position:ASPointNull - sublayouts:sublayouts]; -} - -+ (instancetype)layoutWithLayoutElement:(id)layoutElement size:(CGSize)size NS_RETURNS_RETAINED -{ - return [self layoutWithLayoutElement:layoutElement - size:size - position:ASPointNull - sublayouts:nil]; -} - -- (void)dealloc -{ - if (_retainSublayoutElements.load()) { - for (ASLayout *sublayout in _sublayouts) { - // We retained this, so there's no risk of it deallocating on us. - if (CFTypeRef cfElement = (__bridge CFTypeRef)sublayout->_layoutElement) { - CFRelease(cfElement); - } - } - } -} - -#pragma mark - Sublayout Elements Caching - -- (void)retainSublayoutElements -{ - if (_retainSublayoutElements.exchange(true)) { - return; - } - - for (ASLayout *sublayout in _sublayouts) { - // CFBridgingRetain atomically casts and retains. We need the atomicity. - CFBridgingRetain(sublayout->_layoutElement); - } -} - -#pragma mark - Layout Flattening - -- (BOOL)isFlattened -{ - // A layout is flattened if its position is null, and all of its sublayouts are of type displaynode with no sublayouts. - if (!ASPointIsNull(_position)) { - return NO; - } - - for (ASLayout *sublayout in _sublayouts) { - if (ASLayoutIsDisplayNodeType(sublayout) == NO || sublayout->_sublayouts.count > 0) { - return NO; - } - } - - return YES; -} - -- (ASLayout *)filteredNodeLayoutTree NS_RETURNS_RETAINED -{ - if ([self isFlattened]) { - // All flattened layouts must retain sublayout elements until they are applied. - [self retainSublayoutElements]; - return self; - } - - struct Context { - unowned ASLayout *layout; - CGPoint absolutePosition; - }; - - // Queue used to keep track of sublayouts while traversing this layout in a DFS fashion. - std::deque queue; - for (ASLayout *sublayout in _sublayouts) { - queue.push_back({sublayout, sublayout.position}); - } - - std::vector flattenedSublayouts; - - while (!queue.empty()) { - const Context context = std::move(queue.front()); - queue.pop_front(); - - unowned ASLayout *layout = context.layout; - // Direct ivar access to avoid retain/release, use existing +1. - const NSUInteger sublayoutsCount = layout->_sublayouts.count; - const CGPoint absolutePosition = context.absolutePosition; - - if (ASLayoutIsDisplayNodeType(layout)) { - if (sublayoutsCount > 0 || CGPointEqualToPoint(ASCeilPointValues(absolutePosition), layout.position) == NO) { - // Only create a new layout if the existing one can't be reused, which means it has either some sublayouts or an invalid absolute position. - const auto newLayout = [ASLayout layoutWithLayoutElement:layout->_layoutElement - size:layout.size - position:absolutePosition - sublayouts:@[]]; - flattenedSublayouts.push_back(newLayout); - } else { - flattenedSublayouts.push_back(layout); - } - } else if (sublayoutsCount > 0) { - // Fast-reverse-enumerate the sublayouts array by copying it into a C-array and push_front'ing each into the queue. - unowned ASLayout *rawSublayouts[sublayoutsCount]; - [layout->_sublayouts getObjects:rawSublayouts range:NSMakeRange(0, sublayoutsCount)]; - for (NSInteger i = sublayoutsCount - 1; i >= 0; i--) { - queue.push_front({rawSublayouts[i], absolutePosition + rawSublayouts[i].position}); - } - } - } - - NSArray *array = [NSArray arrayByTransferring:flattenedSublayouts.data() count:flattenedSublayouts.size()]; - // flattenedSublayouts is now all nils. - - ASLayout *layout = [ASLayout layoutWithLayoutElement:_layoutElement size:_size sublayouts:array]; - // All flattened layouts must retain sublayout elements until they are applied. - [layout retainSublayoutElements]; - return layout; -} - -#pragma mark - Equality Checking - -- (BOOL)isEqual:(id)object -{ - if (self == object) return YES; - - ASLayout *layout = ASDynamicCast(object, ASLayout); - if (layout == nil) { - return NO; - } - - if (!CGSizeEqualToSize(_size, layout.size)) return NO; - - if (!((ASPointIsNull(self.position) && ASPointIsNull(layout.position)) - || CGPointEqualToPoint(self.position, layout.position))) return NO; - if (_layoutElement != layout.layoutElement) return NO; - - if (!ASObjectIsEqual(_sublayouts, layout.sublayouts)) { - return NO; - } - - return YES; -} - -#pragma mark - Accessors - -- (ASLayoutElementType)type -{ - return _layoutElementType; -} - -- (CGRect)frameForElement:(id)layoutElement -{ - for (ASLayout *l in _sublayouts) { - if (l->_layoutElement == layoutElement) { - return l.frame; - } - } - return CGRectNull; -} - -- (CGRect)frame -{ - CGRect subnodeFrame = CGRectZero; - CGPoint adjustedOrigin = _position; - if (isfinite(adjustedOrigin.x) == NO) { - ASDisplayNodeAssert(0, @"Layout has an invalid position"); - adjustedOrigin.x = 0; - } - if (isfinite(adjustedOrigin.y) == NO) { - ASDisplayNodeAssert(0, @"Layout has an invalid position"); - adjustedOrigin.y = 0; - } - subnodeFrame.origin = adjustedOrigin; - - CGSize adjustedSize = _size; - if (isfinite(adjustedSize.width) == NO) { - ASDisplayNodeAssert(0, @"Layout has an invalid size"); - adjustedSize.width = 0; - } - if (isfinite(adjustedSize.height) == NO) { - ASDisplayNodeAssert(0, @"Layout has an invalid position"); - adjustedSize.height = 0; - } - subnodeFrame.size = adjustedSize; - - return subnodeFrame; -} - -#pragma mark - Description - -- (NSMutableArray *)propertiesForDescription -{ - NSMutableArray *result = [NSMutableArray array]; - [result addObject:@{ @"size" : [NSValue valueWithCGSize:self.size] }]; - - if (id layoutElement = self.layoutElement) { - [result addObject:@{ @"layoutElement" : layoutElement }]; - } - - const auto pos = self.position; - if (!ASPointIsNull(pos)) { - [result addObject:@{ @"position" : [NSValue valueWithCGPoint:pos] }]; - } - return result; -} - -- (NSString *)description -{ - return ASObjectDescriptionMake(self, [self propertiesForDescription]); -} - -- (NSString *)recursiveDescription -{ - return [self _recursiveDescriptionForLayout:self level:0]; -} - -- (NSString *)_recursiveDescriptionForLayout:(ASLayout *)layout level:(NSUInteger)level -{ - NSMutableString *description = [NSMutableString string]; - [description appendString:descriptionIndents(level)]; - [description appendString:[layout description]]; - for (ASLayout *sublayout in layout.sublayouts) { - [description appendString:@"\n"]; - [description appendString:[self _recursiveDescriptionForLayout:sublayout level:level + 1]]; - } - return description; -} - -@end - -ASLayout *ASCalculateLayout(id layoutElement, const ASSizeRange sizeRange, const CGSize parentSize) -{ - NSCParameterAssert(layoutElement != nil); - - return [layoutElement layoutThatFits:sizeRange parentSize:parentSize]; -} - -ASLayout *ASCalculateRootLayout(id rootLayoutElement, const ASSizeRange sizeRange) -{ - ASLayout *layout = ASCalculateLayout(rootLayoutElement, sizeRange, sizeRange.max); - // Here could specific verfication happen - return layout; -} diff --git a/submodules/AsyncDisplayKit/Source/ASLayoutElement.mm b/submodules/AsyncDisplayKit/Source/ASLayoutElement.mm deleted file mode 100644 index 887f609e99..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASLayoutElement.mm +++ /dev/null @@ -1,843 +0,0 @@ -// -// ASLayoutElement.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import -#import -#import -#import -#import -#import - -#import -#include - -using AS::MutexLocker; - -#if YOGA - #import YOGA_HEADER_PATH - #import -#endif - -#pragma mark - ASLayoutElementContext - -@implementation ASLayoutElementContext - -- (instancetype)init -{ - if (self = [super init]) { - _transitionID = ASLayoutElementContextDefaultTransitionID; - } - return self; -} - -@end - -CGFloat const ASLayoutElementParentDimensionUndefined = NAN; -CGSize const ASLayoutElementParentSizeUndefined = {ASLayoutElementParentDimensionUndefined, ASLayoutElementParentDimensionUndefined}; - -int32_t const ASLayoutElementContextInvalidTransitionID = 0; -int32_t const ASLayoutElementContextDefaultTransitionID = ASLayoutElementContextInvalidTransitionID + 1; - -#if AS_TLS_AVAILABLE - -static _Thread_local __unsafe_unretained ASLayoutElementContext *tls_context; - -void ASLayoutElementPushContext(ASLayoutElementContext *context) -{ - // NOTE: It would be easy to support nested contexts – just use an NSMutableArray here. - ASDisplayNodeCAssertNil(tls_context, @"Nested ASLayoutElementContexts aren't supported."); - - tls_context = (__bridge ASLayoutElementContext *)(__bridge_retained CFTypeRef)context; -} - -ASLayoutElementContext *ASLayoutElementGetCurrentContext() -{ - // Don't retain here. Caller will retain if it wants to! - return tls_context; -} - -void ASLayoutElementPopContext() -{ - ASDisplayNodeCAssertNotNil(tls_context, @"Attempt to pop context when there wasn't a context!"); - CFRelease((__bridge CFTypeRef)tls_context); - tls_context = nil; -} - -#else - -static pthread_key_t ASLayoutElementContextKey() { - static pthread_key_t k; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - pthread_key_create(&k, NULL); - }); - return k; -} -void ASLayoutElementPushContext(ASLayoutElementContext *context) -{ - // NOTE: It would be easy to support nested contexts – just use an NSMutableArray here. - ASDisplayNodeCAssertNil(pthread_getspecific(ASLayoutElementContextKey()), @"Nested ASLayoutElementContexts aren't supported."); - - const auto cfCtx = (__bridge_retained CFTypeRef)context; - pthread_setspecific(ASLayoutElementContextKey(), cfCtx); -} - -ASLayoutElementContext *ASLayoutElementGetCurrentContext() -{ - // Don't retain here. Caller will retain if it wants to! - const auto ctxPtr = pthread_getspecific(ASLayoutElementContextKey()); - return (__bridge ASLayoutElementContext *)ctxPtr; -} - -void ASLayoutElementPopContext() -{ - const auto ctx = (CFTypeRef)pthread_getspecific(ASLayoutElementContextKey()); - ASDisplayNodeCAssertNotNil(ctx, @"Attempt to pop context when there wasn't a context!"); - CFRelease(ctx); - pthread_setspecific(ASLayoutElementContextKey(), NULL); -} - -#endif // AS_TLS_AVAILABLE - -#pragma mark - ASLayoutElementStyle - -NSString * const ASLayoutElementStyleWidthProperty = @"ASLayoutElementStyleWidthProperty"; -NSString * const ASLayoutElementStyleMinWidthProperty = @"ASLayoutElementStyleMinWidthProperty"; -NSString * const ASLayoutElementStyleMaxWidthProperty = @"ASLayoutElementStyleMaxWidthProperty"; - -NSString * const ASLayoutElementStyleHeightProperty = @"ASLayoutElementStyleHeightProperty"; -NSString * const ASLayoutElementStyleMinHeightProperty = @"ASLayoutElementStyleMinHeightProperty"; -NSString * const ASLayoutElementStyleMaxHeightProperty = @"ASLayoutElementStyleMaxHeightProperty"; - -NSString * const ASLayoutElementStyleSpacingBeforeProperty = @"ASLayoutElementStyleSpacingBeforeProperty"; -NSString * const ASLayoutElementStyleSpacingAfterProperty = @"ASLayoutElementStyleSpacingAfterProperty"; -NSString * const ASLayoutElementStyleFlexGrowProperty = @"ASLayoutElementStyleFlexGrowProperty"; -NSString * const ASLayoutElementStyleFlexShrinkProperty = @"ASLayoutElementStyleFlexShrinkProperty"; -NSString * const ASLayoutElementStyleFlexBasisProperty = @"ASLayoutElementStyleFlexBasisProperty"; -NSString * const ASLayoutElementStyleAlignSelfProperty = @"ASLayoutElementStyleAlignSelfProperty"; -NSString * const ASLayoutElementStyleAscenderProperty = @"ASLayoutElementStyleAscenderProperty"; -NSString * const ASLayoutElementStyleDescenderProperty = @"ASLayoutElementStyleDescenderProperty"; - -NSString * const ASLayoutElementStyleLayoutPositionProperty = @"ASLayoutElementStyleLayoutPositionProperty"; - -#if YOGA -NSString * const ASYogaFlexWrapProperty = @"ASLayoutElementStyleLayoutFlexWrapProperty"; -NSString * const ASYogaFlexDirectionProperty = @"ASYogaFlexDirectionProperty"; -NSString * const ASYogaDirectionProperty = @"ASYogaDirectionProperty"; -NSString * const ASYogaSpacingProperty = @"ASYogaSpacingProperty"; -NSString * const ASYogaJustifyContentProperty = @"ASYogaJustifyContentProperty"; -NSString * const ASYogaAlignItemsProperty = @"ASYogaAlignItemsProperty"; -NSString * const ASYogaPositionTypeProperty = @"ASYogaPositionTypeProperty"; -NSString * const ASYogaPositionProperty = @"ASYogaPositionProperty"; -NSString * const ASYogaMarginProperty = @"ASYogaMarginProperty"; -NSString * const ASYogaPaddingProperty = @"ASYogaPaddingProperty"; -NSString * const ASYogaBorderProperty = @"ASYogaBorderProperty"; -NSString * const ASYogaAspectRatioProperty = @"ASYogaAspectRatioProperty"; -#endif - -#define ASLayoutElementStyleSetSizeWithScope(x) \ - __instanceLock__.lock(); \ - ASLayoutElementSize newSize = _size.load(); \ - { x } \ - _size.store(newSize); \ - __instanceLock__.unlock(); - -#define ASLayoutElementStyleCallDelegate(propertyName)\ -do {\ - [self propertyDidChange:propertyName];\ - [_delegate style:self propertyDidChange:propertyName];\ -} while(0) - -@implementation ASLayoutElementStyle { - AS::RecursiveMutex __instanceLock__; - ASLayoutElementStyleExtensions _extensions; - - std::atomic _size; - std::atomic _spacingBefore; - std::atomic _spacingAfter; - std::atomic _flexGrow; - std::atomic _flexShrink; - std::atomic _flexBasis; - std::atomic _alignSelf; - std::atomic _ascender; - std::atomic _descender; - std::atomic _layoutPosition; - -#if YOGA - YGNodeRef _yogaNode; - std::atomic _flexWrap; - std::atomic _flexDirection; - std::atomic _direction; - std::atomic _justifyContent; - std::atomic _alignItems; - std::atomic _positionType; - std::atomic _position; - std::atomic _margin; - std::atomic _padding; - std::atomic _border; - std::atomic _aspectRatio; - ASStackLayoutAlignItems _parentAlignStyle; -#endif -} - -@dynamic width, height, minWidth, maxWidth, minHeight, maxHeight; -@dynamic preferredSize, minSize, maxSize, preferredLayoutSize, minLayoutSize, maxLayoutSize; - -#pragma mark - Lifecycle - -- (instancetype)initWithDelegate:(id)delegate -{ - self = [self init]; - if (self) { - _delegate = delegate; - } - return self; -} - -- (instancetype)init -{ - self = [super init]; - if (self) { - _size = ASLayoutElementSizeMake(); -#if YOGA - _parentAlignStyle = ASStackLayoutAlignItemsNotSet; -#endif - } - return self; -} - -ASSynthesizeLockingMethodsWithMutex(__instanceLock__) - -#pragma mark - ASLayoutElementStyleSize - -- (ASLayoutElementSize)size -{ - return _size.load(); -} - -- (void)setSize:(ASLayoutElementSize)size -{ - ASLayoutElementStyleSetSizeWithScope({ - newSize = size; - }); - // No CallDelegate method as ASLayoutElementSize is currently internal. -} - -#pragma mark - ASLayoutElementStyleSizeForwarding - -- (ASDimension)width -{ - return _size.load().width; -} - -- (void)setWidth:(ASDimension)width -{ - ASLayoutElementStyleSetSizeWithScope({ - newSize.width = width; - }); - ASLayoutElementStyleCallDelegate(ASLayoutElementStyleWidthProperty); -} - -- (ASDimension)height -{ - return _size.load().height; -} - -- (void)setHeight:(ASDimension)height -{ - ASLayoutElementStyleSetSizeWithScope({ - newSize.height = height; - }); - ASLayoutElementStyleCallDelegate(ASLayoutElementStyleHeightProperty); -} - -- (ASDimension)minWidth -{ - return _size.load().minWidth; -} - -- (void)setMinWidth:(ASDimension)minWidth -{ - ASLayoutElementStyleSetSizeWithScope({ - newSize.minWidth = minWidth; - }); - ASLayoutElementStyleCallDelegate(ASLayoutElementStyleMinWidthProperty); -} - -- (ASDimension)maxWidth -{ - return _size.load().maxWidth; -} - -- (void)setMaxWidth:(ASDimension)maxWidth -{ - ASLayoutElementStyleSetSizeWithScope({ - newSize.maxWidth = maxWidth; - }); - ASLayoutElementStyleCallDelegate(ASLayoutElementStyleMaxWidthProperty); -} - -- (ASDimension)minHeight -{ - return _size.load().minHeight; -} - -- (void)setMinHeight:(ASDimension)minHeight -{ - ASLayoutElementStyleSetSizeWithScope({ - newSize.minHeight = minHeight; - }); - ASLayoutElementStyleCallDelegate(ASLayoutElementStyleMinHeightProperty); -} - -- (ASDimension)maxHeight -{ - return _size.load().maxHeight; -} - -- (void)setMaxHeight:(ASDimension)maxHeight -{ - ASLayoutElementStyleSetSizeWithScope({ - newSize.maxHeight = maxHeight; - }); - ASLayoutElementStyleCallDelegate(ASLayoutElementStyleMaxHeightProperty); -} - - -#pragma mark - ASLayoutElementStyleSizeHelpers - -- (void)setPreferredSize:(CGSize)preferredSize -{ - ASLayoutElementStyleSetSizeWithScope({ - newSize.width = ASDimensionMakeWithPoints(preferredSize.width); - newSize.height = ASDimensionMakeWithPoints(preferredSize.height); - }); - ASLayoutElementStyleCallDelegate(ASLayoutElementStyleWidthProperty); - ASLayoutElementStyleCallDelegate(ASLayoutElementStyleHeightProperty); -} - -- (CGSize)preferredSize -{ - ASLayoutElementSize size = _size.load(); - if (size.width.unit == ASDimensionUnitFraction) { - NSCAssert(NO, @"Cannot get preferredSize of element with fractional width. Width: %@.", NSStringFromASDimension(size.width)); - return CGSizeZero; - } - - if (size.height.unit == ASDimensionUnitFraction) { - NSCAssert(NO, @"Cannot get preferredSize of element with fractional height. Height: %@.", NSStringFromASDimension(size.height)); - return CGSizeZero; - } - - return CGSizeMake(size.width.value, size.height.value); -} - -- (void)setMinSize:(CGSize)minSize -{ - ASLayoutElementStyleSetSizeWithScope({ - newSize.minWidth = ASDimensionMakeWithPoints(minSize.width); - newSize.minHeight = ASDimensionMakeWithPoints(minSize.height); - }); - ASLayoutElementStyleCallDelegate(ASLayoutElementStyleMinWidthProperty); - ASLayoutElementStyleCallDelegate(ASLayoutElementStyleMinHeightProperty); -} - -- (void)setMaxSize:(CGSize)maxSize -{ - ASLayoutElementStyleSetSizeWithScope({ - newSize.maxWidth = ASDimensionMakeWithPoints(maxSize.width); - newSize.maxHeight = ASDimensionMakeWithPoints(maxSize.height); - }); - ASLayoutElementStyleCallDelegate(ASLayoutElementStyleMaxWidthProperty); - ASLayoutElementStyleCallDelegate(ASLayoutElementStyleMaxHeightProperty); -} - -- (ASLayoutSize)preferredLayoutSize -{ - ASLayoutElementSize size = _size.load(); - return ASLayoutSizeMake(size.width, size.height); -} - -- (void)setPreferredLayoutSize:(ASLayoutSize)preferredLayoutSize -{ - ASLayoutElementStyleSetSizeWithScope({ - newSize.width = preferredLayoutSize.width; - newSize.height = preferredLayoutSize.height; - }); - ASLayoutElementStyleCallDelegate(ASLayoutElementStyleWidthProperty); - ASLayoutElementStyleCallDelegate(ASLayoutElementStyleHeightProperty); -} - -- (ASLayoutSize)minLayoutSize -{ - ASLayoutElementSize size = _size.load(); - return ASLayoutSizeMake(size.minWidth, size.minHeight); -} - -- (void)setMinLayoutSize:(ASLayoutSize)minLayoutSize -{ - ASLayoutElementStyleSetSizeWithScope({ - newSize.minWidth = minLayoutSize.width; - newSize.minHeight = minLayoutSize.height; - }); - ASLayoutElementStyleCallDelegate(ASLayoutElementStyleMinWidthProperty); - ASLayoutElementStyleCallDelegate(ASLayoutElementStyleMinHeightProperty); -} - -- (ASLayoutSize)maxLayoutSize -{ - ASLayoutElementSize size = _size.load(); - return ASLayoutSizeMake(size.maxWidth, size.maxHeight); -} - -- (void)setMaxLayoutSize:(ASLayoutSize)maxLayoutSize -{ - ASLayoutElementStyleSetSizeWithScope({ - newSize.maxWidth = maxLayoutSize.width; - newSize.maxHeight = maxLayoutSize.height; - }); - ASLayoutElementStyleCallDelegate(ASLayoutElementStyleMaxWidthProperty); - ASLayoutElementStyleCallDelegate(ASLayoutElementStyleMaxHeightProperty); -} - -#pragma mark - ASStackLayoutElement - -- (void)setSpacingBefore:(CGFloat)spacingBefore -{ - _spacingBefore.store(spacingBefore); - ASLayoutElementStyleCallDelegate(ASLayoutElementStyleSpacingBeforeProperty); -} - -- (CGFloat)spacingBefore -{ - return _spacingBefore.load(); -} - -- (void)setSpacingAfter:(CGFloat)spacingAfter -{ - _spacingAfter.store(spacingAfter); - ASLayoutElementStyleCallDelegate(ASLayoutElementStyleSpacingAfterProperty); -} - -- (CGFloat)spacingAfter -{ - return _spacingAfter.load(); -} - -- (void)setFlexGrow:(CGFloat)flexGrow -{ - _flexGrow.store(flexGrow); - ASLayoutElementStyleCallDelegate(ASLayoutElementStyleFlexGrowProperty); -} - -- (CGFloat)flexGrow -{ - return _flexGrow.load(); -} - -- (void)setFlexShrink:(CGFloat)flexShrink -{ - _flexShrink.store(flexShrink); - ASLayoutElementStyleCallDelegate(ASLayoutElementStyleFlexShrinkProperty); -} - -- (CGFloat)flexShrink -{ - return _flexShrink.load(); -} - -- (void)setFlexBasis:(ASDimension)flexBasis -{ - _flexBasis.store(flexBasis); - ASLayoutElementStyleCallDelegate(ASLayoutElementStyleFlexBasisProperty); -} - -- (ASDimension)flexBasis -{ - return _flexBasis.load(); -} - -- (void)setAlignSelf:(ASStackLayoutAlignSelf)alignSelf -{ - _alignSelf.store(alignSelf); - ASLayoutElementStyleCallDelegate(ASLayoutElementStyleAlignSelfProperty); -} - -- (ASStackLayoutAlignSelf)alignSelf -{ - return _alignSelf.load(); -} - -- (void)setAscender:(CGFloat)ascender -{ - _ascender.store(ascender); - ASLayoutElementStyleCallDelegate(ASLayoutElementStyleAscenderProperty); -} - -- (CGFloat)ascender -{ - return _ascender.load(); -} - -- (void)setDescender:(CGFloat)descender -{ - _descender.store(descender); - ASLayoutElementStyleCallDelegate(ASLayoutElementStyleDescenderProperty); -} - -- (CGFloat)descender -{ - return _descender.load(); -} - -#pragma mark - ASAbsoluteLayoutElement - -- (void)setLayoutPosition:(CGPoint)layoutPosition -{ - _layoutPosition.store(layoutPosition); - ASLayoutElementStyleCallDelegate(ASLayoutElementStyleLayoutPositionProperty); -} - -- (CGPoint)layoutPosition -{ - return _layoutPosition.load(); -} - -#pragma mark - Extensions - -- (void)setLayoutOptionExtensionBool:(BOOL)value atIndex:(int)idx -{ - NSCAssert(idx < kMaxLayoutElementBoolExtensions, @"Setting index outside of max bool extensions space"); - - MutexLocker l(__instanceLock__); - _extensions.boolExtensions[idx] = value; -} - -- (BOOL)layoutOptionExtensionBoolAtIndex:(int)idx\ -{ - NSCAssert(idx < kMaxLayoutElementBoolExtensions, @"Accessing index outside of max bool extensions space"); - - MutexLocker l(__instanceLock__); - return _extensions.boolExtensions[idx]; -} - -- (void)setLayoutOptionExtensionInteger:(NSInteger)value atIndex:(int)idx -{ - NSCAssert(idx < kMaxLayoutElementStateIntegerExtensions, @"Setting index outside of max integer extensions space"); - - MutexLocker l(__instanceLock__); - _extensions.integerExtensions[idx] = value; -} - -- (NSInteger)layoutOptionExtensionIntegerAtIndex:(int)idx -{ - NSCAssert(idx < kMaxLayoutElementStateIntegerExtensions, @"Accessing index outside of max integer extensions space"); - - MutexLocker l(__instanceLock__); - return _extensions.integerExtensions[idx]; -} - -- (void)setLayoutOptionExtensionEdgeInsets:(UIEdgeInsets)value atIndex:(int)idx -{ - NSCAssert(idx < kMaxLayoutElementStateEdgeInsetExtensions, @"Setting index outside of max edge insets extensions space"); - - MutexLocker l(__instanceLock__); - _extensions.edgeInsetsExtensions[idx] = value; -} - -- (UIEdgeInsets)layoutOptionExtensionEdgeInsetsAtIndex:(int)idx -{ - NSCAssert(idx < kMaxLayoutElementStateEdgeInsetExtensions, @"Accessing index outside of max edge insets extensions space"); - - MutexLocker l(__instanceLock__); - return _extensions.edgeInsetsExtensions[idx]; -} - -#pragma mark - Debugging - -- (NSString *)description -{ - return ASObjectDescriptionMake(self, [self propertiesForDescription]); -} - -- (NSMutableArray *)propertiesForDescription -{ - NSMutableArray *result = [NSMutableArray array]; - - if ((self.minLayoutSize.width.unit != ASDimensionUnitAuto || - self.minLayoutSize.height.unit != ASDimensionUnitAuto)) { - [result addObject:@{ @"minLayoutSize" : NSStringFromASLayoutSize(self.minLayoutSize) }]; - } - - if ((self.preferredLayoutSize.width.unit != ASDimensionUnitAuto || - self.preferredLayoutSize.height.unit != ASDimensionUnitAuto)) { - [result addObject:@{ @"preferredSize" : NSStringFromASLayoutSize(self.preferredLayoutSize) }]; - } - - if ((self.maxLayoutSize.width.unit != ASDimensionUnitAuto || - self.maxLayoutSize.height.unit != ASDimensionUnitAuto)) { - [result addObject:@{ @"maxLayoutSize" : NSStringFromASLayoutSize(self.maxLayoutSize) }]; - } - - if (self.alignSelf != ASStackLayoutAlignSelfAuto) { - [result addObject:@{ @"alignSelf" : [@[@"ASStackLayoutAlignSelfAuto", - @"ASStackLayoutAlignSelfStart", - @"ASStackLayoutAlignSelfEnd", - @"ASStackLayoutAlignSelfCenter", - @"ASStackLayoutAlignSelfStretch"] objectAtIndex:self.alignSelf] }]; - } - - if (self.ascender != 0) { - [result addObject:@{ @"ascender" : @(self.ascender) }]; - } - - if (self.descender != 0) { - [result addObject:@{ @"descender" : @(self.descender) }]; - } - - if (ASDimensionEqualToDimension(self.flexBasis, ASDimensionAuto) == NO) { - [result addObject:@{ @"flexBasis" : NSStringFromASDimension(self.flexBasis) }]; - } - - if (self.flexGrow != 0) { - [result addObject:@{ @"flexGrow" : @(self.flexGrow) }]; - } - - if (self.flexShrink != 0) { - [result addObject:@{ @"flexShrink" : @(self.flexShrink) }]; - } - - if (self.spacingAfter != 0) { - [result addObject:@{ @"spacingAfter" : @(self.spacingAfter) }]; - } - - if (self.spacingBefore != 0) { - [result addObject:@{ @"spacingBefore" : @(self.spacingBefore) }]; - } - - if (CGPointEqualToPoint(self.layoutPosition, CGPointZero) == NO) { - [result addObject:@{ @"layoutPosition" : [NSValue valueWithCGPoint:self.layoutPosition] }]; - } - - return result; -} - -- (void)propertyDidChange:(NSString *)propertyName -{ -#if YOGA - /* TODO(appleguy): STYLE SETTER METHODS LEFT TO IMPLEMENT - void YGNodeStyleSetOverflow(YGNodeRef node, YGOverflow overflow); - void YGNodeStyleSetFlex(YGNodeRef node, float flex); - */ - - if (_yogaNode == NULL) { - return; - } - // Because the NSStrings used to identify each property are const, use efficient pointer comparison. - if (propertyName == ASLayoutElementStyleWidthProperty) { - YGNODE_STYLE_SET_DIMENSION(_yogaNode, Width, self.width); - } - else if (propertyName == ASLayoutElementStyleMinWidthProperty) { - YGNODE_STYLE_SET_DIMENSION(_yogaNode, MinWidth, self.minWidth); - } - else if (propertyName == ASLayoutElementStyleMaxWidthProperty) { - YGNODE_STYLE_SET_DIMENSION(_yogaNode, MaxWidth, self.maxWidth); - } - else if (propertyName == ASLayoutElementStyleHeightProperty) { - YGNODE_STYLE_SET_DIMENSION(_yogaNode, Height, self.height); - } - else if (propertyName == ASLayoutElementStyleMinHeightProperty) { - YGNODE_STYLE_SET_DIMENSION(_yogaNode, MinHeight, self.minHeight); - } - else if (propertyName == ASLayoutElementStyleMaxHeightProperty) { - YGNODE_STYLE_SET_DIMENSION(_yogaNode, MaxHeight, self.maxHeight); - } - else if (propertyName == ASLayoutElementStyleFlexGrowProperty) { - YGNodeStyleSetFlexGrow(_yogaNode, self.flexGrow); - } - else if (propertyName == ASLayoutElementStyleFlexShrinkProperty) { - YGNodeStyleSetFlexShrink(_yogaNode, self.flexShrink); - } - else if (propertyName == ASLayoutElementStyleFlexBasisProperty) { - YGNODE_STYLE_SET_DIMENSION(_yogaNode, FlexBasis, self.flexBasis); - } - else if (propertyName == ASLayoutElementStyleAlignSelfProperty) { - YGNodeStyleSetAlignSelf(_yogaNode, yogaAlignSelf(self.alignSelf)); - } - else if (propertyName == ASYogaFlexWrapProperty) { - YGNodeStyleSetFlexWrap(_yogaNode, self.flexWrap); - } - else if (propertyName == ASYogaFlexDirectionProperty) { - YGNodeStyleSetFlexDirection(_yogaNode, yogaFlexDirection(self.flexDirection)); - } - else if (propertyName == ASYogaDirectionProperty) { - YGNodeStyleSetDirection(_yogaNode, self.direction); - } - else if (propertyName == ASYogaJustifyContentProperty) { - YGNodeStyleSetJustifyContent(_yogaNode, yogaJustifyContent(self.justifyContent)); - } - else if (propertyName == ASYogaAlignItemsProperty) { - ASStackLayoutAlignItems alignItems = self.alignItems; - if (alignItems != ASStackLayoutAlignItemsNotSet) { - YGNodeStyleSetAlignItems(_yogaNode, yogaAlignItems(alignItems)); - } - } - else if (propertyName == ASYogaPositionTypeProperty) { - YGNodeStyleSetPositionType(_yogaNode, self.positionType); - } - else if (propertyName == ASYogaPositionProperty) { - ASEdgeInsets position = self.position; - YGEdge edge = YGEdgeLeft; - for (int i = 0; i < YGEdgeAll + 1; ++i) { - YGNODE_STYLE_SET_DIMENSION_WITH_EDGE(_yogaNode, Position, dimensionForEdgeWithEdgeInsets(edge, position), edge); - edge = (YGEdge)(edge + 1); - } - } - else if (propertyName == ASYogaMarginProperty) { - ASEdgeInsets margin = self.margin; - YGEdge edge = YGEdgeLeft; - for (int i = 0; i < YGEdgeAll + 1; ++i) { - YGNODE_STYLE_SET_DIMENSION_WITH_EDGE(_yogaNode, Margin, dimensionForEdgeWithEdgeInsets(edge, margin), edge); - edge = (YGEdge)(edge + 1); - } - } - else if (propertyName == ASYogaPaddingProperty) { - ASEdgeInsets padding = self.padding; - YGEdge edge = YGEdgeLeft; - for (int i = 0; i < YGEdgeAll + 1; ++i) { - YGNODE_STYLE_SET_DIMENSION_WITH_EDGE(_yogaNode, Padding, dimensionForEdgeWithEdgeInsets(edge, padding), edge); - edge = (YGEdge)(edge + 1); - } - } - else if (propertyName == ASYogaBorderProperty) { - ASEdgeInsets border = self.border; - YGEdge edge = YGEdgeLeft; - for (int i = 0; i < YGEdgeAll + 1; ++i) { - YGNODE_STYLE_SET_FLOAT_WITH_EDGE(_yogaNode, Border, dimensionForEdgeWithEdgeInsets(edge, border), edge); - edge = (YGEdge)(edge + 1); - } - } - else if (propertyName == ASYogaAspectRatioProperty) { - CGFloat aspectRatio = self.aspectRatio; - if (aspectRatio > FLT_EPSILON && aspectRatio < CGFLOAT_MAX / 2.0) { - YGNodeStyleSetAspectRatio(_yogaNode, aspectRatio); - } - } -#endif -} - -#pragma mark - Yoga Flexbox Properties - -#if YOGA - -+ (void)initialize -{ - [super initialize]; - YGConfigSetPointScaleFactor(YGConfigGetDefault(), ASScreenScale()); - // Yoga recommends using Web Defaults for all new projects. This will be enabled for Texture very soon. - //YGConfigSetUseWebDefaults(YGConfigGetDefault(), true); -} - -- (YGNodeRef)yogaNode -{ - return _yogaNode; -} - -- (YGNodeRef)yogaNodeCreateIfNeeded -{ - if (_yogaNode == NULL) { - _yogaNode = YGNodeNew(); - } - return _yogaNode; -} - -- (void)destroyYogaNode -{ - if (_yogaNode != NULL) { - // Release the __bridge_retained Context object. - ASLayoutElementYogaUpdateMeasureFunc(_yogaNode, nil); - YGNodeFree(_yogaNode); - _yogaNode = NULL; - } -} - -- (void)dealloc -{ - [self destroyYogaNode]; -} - -- (YGWrap)flexWrap { return _flexWrap.load(); } -- (ASStackLayoutDirection)flexDirection { return _flexDirection.load(); } -- (YGDirection)direction { return _direction.load(); } -- (ASStackLayoutJustifyContent)justifyContent { return _justifyContent.load(); } -- (ASStackLayoutAlignItems)alignItems { return _alignItems.load(); } -- (YGPositionType)positionType { return _positionType.load(); } -- (ASEdgeInsets)position { return _position.load(); } -- (ASEdgeInsets)margin { return _margin.load(); } -- (ASEdgeInsets)padding { return _padding.load(); } -- (ASEdgeInsets)border { return _border.load(); } -- (CGFloat)aspectRatio { return _aspectRatio.load(); } -// private (ASLayoutElementStylePrivate.h) -- (ASStackLayoutAlignItems)parentAlignStyle { - return _parentAlignStyle; -} - -- (void)setFlexWrap:(YGWrap)flexWrap { - _flexWrap.store(flexWrap); - ASLayoutElementStyleCallDelegate(ASYogaFlexWrapProperty); -} -- (void)setFlexDirection:(ASStackLayoutDirection)flexDirection { - _flexDirection.store(flexDirection); - ASLayoutElementStyleCallDelegate(ASYogaFlexDirectionProperty); -} -- (void)setDirection:(YGDirection)direction { - _direction.store(direction); - ASLayoutElementStyleCallDelegate(ASYogaDirectionProperty); -} -- (void)setJustifyContent:(ASStackLayoutJustifyContent)justify { - _justifyContent.store(justify); - ASLayoutElementStyleCallDelegate(ASYogaJustifyContentProperty); -} -- (void)setAlignItems:(ASStackLayoutAlignItems)alignItems { - _alignItems.store(alignItems); - ASLayoutElementStyleCallDelegate(ASYogaAlignItemsProperty); -} -- (void)setPositionType:(YGPositionType)positionType { - _positionType.store(positionType); - ASLayoutElementStyleCallDelegate(ASYogaPositionTypeProperty); -} -- (void)setPosition:(ASEdgeInsets)position { - _position.store(position); - ASLayoutElementStyleCallDelegate(ASYogaPositionProperty); -} -- (void)setMargin:(ASEdgeInsets)margin { - _margin.store(margin); - ASLayoutElementStyleCallDelegate(ASYogaMarginProperty); -} -- (void)setPadding:(ASEdgeInsets)padding { - _padding.store(padding); - ASLayoutElementStyleCallDelegate(ASYogaPaddingProperty); -} -- (void)setBorder:(ASEdgeInsets)border { - _border.store(border); - ASLayoutElementStyleCallDelegate(ASYogaBorderProperty); -} -- (void)setAspectRatio:(CGFloat)aspectRatio { - _aspectRatio.store(aspectRatio); - ASLayoutElementStyleCallDelegate(ASYogaAspectRatioProperty); -} -// private (ASLayoutElementStylePrivate.h) -- (void)setParentAlignStyle:(ASStackLayoutAlignItems)style { - _parentAlignStyle = style; -} - -#endif /* YOGA */ - -@end diff --git a/submodules/AsyncDisplayKit/Source/ASLayoutElementStylePrivate.h b/submodules/AsyncDisplayKit/Source/ASLayoutElementStylePrivate.h deleted file mode 100644 index 69e29824ef..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASLayoutElementStylePrivate.h +++ /dev/null @@ -1,31 +0,0 @@ -// -// ASLayoutElementStylePrivate.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#pragma once - -#import -#import - -@interface ASLayoutElementStyle () - -/** - * @abstract The object that acts as the delegate of the style. - * - * @discussion The delegate must adopt the ASLayoutElementStyleDelegate protocol. The delegate is not retained. - */ -@property (nullable, nonatomic, weak) id delegate; - -/** - * @abstract A size constraint that should apply to this ASLayoutElement. - */ -@property (nonatomic, readonly) ASLayoutElementSize size; - -@property (nonatomic, assign) ASStackLayoutAlignItems parentAlignStyle; - -@end diff --git a/submodules/AsyncDisplayKit/Source/ASLayoutManager.h b/submodules/AsyncDisplayKit/Source/ASLayoutManager.h deleted file mode 100644 index 1396bf203e..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASLayoutManager.h +++ /dev/null @@ -1,16 +0,0 @@ -// -// ASLayoutManager.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import - -AS_SUBCLASSING_RESTRICTED -@interface ASLayoutManager : NSLayoutManager - -@end diff --git a/submodules/AsyncDisplayKit/Source/ASLayoutManager.mm b/submodules/AsyncDisplayKit/Source/ASLayoutManager.mm deleted file mode 100644 index 9eca28e489..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASLayoutManager.mm +++ /dev/null @@ -1,42 +0,0 @@ -// -// ASLayoutManager.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import "ASLayoutManager.h" - -@implementation ASLayoutManager - -- (void)showCGGlyphs:(const CGGlyph *)glyphs - positions:(const CGPoint *)positions - count:(NSUInteger)glyphCount - font:(UIFont *)font - matrix:(CGAffineTransform)textMatrix - attributes:(NSDictionary *)attributes - inContext:(CGContextRef)graphicsContext -{ - - // NSLayoutManager has a hard coded internal color for hyperlinks which ignores - // NSForegroundColorAttributeName. To get around this, we force the fill color - // in the current context to match NSForegroundColorAttributeName. - UIColor *foregroundColor = attributes[NSForegroundColorAttributeName]; - - if (foregroundColor) - { - CGContextSetFillColorWithColor(graphicsContext, foregroundColor.CGColor); - } - - [super showCGGlyphs:glyphs - positions:positions - count:glyphCount - font:font - matrix:textMatrix - attributes:attributes - inContext:graphicsContext]; -} - -@end diff --git a/submodules/AsyncDisplayKit/Source/ASLayoutSpec+Subclasses.h b/submodules/AsyncDisplayKit/Source/ASLayoutSpec+Subclasses.h deleted file mode 100644 index 34bf6e069f..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASLayoutSpec+Subclasses.h +++ /dev/null @@ -1,59 +0,0 @@ -// -// ASLayoutSpec+Subclasses.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -@protocol ASLayoutElement; - -@interface ASLayoutSpec (Subclassing) - -/** - * Adds a child with the given identifier to this layout spec. - * - * @param child A child to be added. - * - * @param index An index associated with the child. - * - * @discussion Every ASLayoutSpec must act on at least one child. The ASLayoutSpec base class takes the - * responsibility of holding on to the spec children. Some layout specs, like ASInsetLayoutSpec, - * only require a single child. - * - * For layout specs that require a known number of children (ASBackgroundLayoutSpec, for example) - * a subclass can use the setChild method to set the "primary" child. It should then use this method - * to set any other required children. Ideally a subclass would hide this from the user, and use the - * setChild:forIndex: internally. For example, ASBackgroundLayoutSpec exposes a backgroundChild - * property that behind the scenes is calling setChild:forIndex:. - */ -- (void)setChild:(id)child atIndex:(NSUInteger)index; - -/** - * Returns the child added to this layout spec using the given index. - * - * @param index An identifier associated with the the child. - */ -- (nullable id)childAtIndex:(NSUInteger)index; - -@end - -@interface ASLayout () - -/** - * Position in parent. Default to CGPointNull. - * - * @discussion When being used as a sublayout, this property must not equal CGPointNull. - */ -@property (nonatomic) CGPoint position; - -@end - -NS_ASSUME_NONNULL_END diff --git a/submodules/AsyncDisplayKit/Source/ASLayoutSpec+Subclasses.mm b/submodules/AsyncDisplayKit/Source/ASLayoutSpec+Subclasses.mm deleted file mode 100644 index c1e4e2496a..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASLayoutSpec+Subclasses.mm +++ /dev/null @@ -1,87 +0,0 @@ -// -// ASLayoutSpec+Subclasses.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import "ASLayoutSpec+Subclasses.h" - -#import -#import "ASLayoutSpecPrivate.h" - -#pragma mark - ASNullLayoutSpec - -@interface ASNullLayoutSpec : ASLayoutSpec -- (instancetype)init NS_UNAVAILABLE; -+ (ASNullLayoutSpec *)null; -@end - -@implementation ASNullLayoutSpec : ASLayoutSpec - -+ (ASNullLayoutSpec *)null -{ - static ASNullLayoutSpec *sharedNullLayoutSpec = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - sharedNullLayoutSpec = [[self alloc] init]; - }); - return sharedNullLayoutSpec; -} - -- (BOOL)isMutable -{ - return NO; -} - -- (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize -{ - return [ASLayout layoutWithLayoutElement:self size:CGSizeZero]; -} - -@end - - -#pragma mark - ASLayoutSpec (Subclassing) - -@implementation ASLayoutSpec (Subclassing) - -#pragma mark - Child with index - -- (void)setChild:(id)child atIndex:(NSUInteger)index -{ - ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable"); - - id layoutElement = child ?: [ASNullLayoutSpec null]; - - if (child) { - if (_childrenArray.count < index) { - // Fill up the array with null objects until the index - NSInteger i = _childrenArray.count; - while (i < index) { - _childrenArray[i] = [ASNullLayoutSpec null]; - i++; - } - } - } - - // Replace object at the given index with the layoutElement - _childrenArray[index] = layoutElement; -} - -- (id)childAtIndex:(NSUInteger)index -{ - id layoutElement = nil; - if (index < _childrenArray.count) { - layoutElement = _childrenArray[index]; - } - - // Null layoutElement should not be accessed - ASDisplayNodeAssert(layoutElement != [ASNullLayoutSpec null], @"Access child at index without set a child at that index"); - - return layoutElement; -} - -@end diff --git a/submodules/AsyncDisplayKit/Source/ASLayoutSpec.mm b/submodules/AsyncDisplayKit/Source/ASLayoutSpec.mm deleted file mode 100644 index 6123e4d734..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASLayoutSpec.mm +++ /dev/null @@ -1,338 +0,0 @@ -// -// ASLayoutSpec.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import "ASLayoutSpecPrivate.h" - -#import "ASLayoutSpec+Subclasses.h" - -#import -#import "ASLayoutElementStylePrivate.h" -#import -#import -#import - -#import -#import -#import - -@implementation ASLayoutSpec - -// Dynamic properties for ASLayoutElements -@dynamic layoutElementType; -@synthesize debugName = _debugName; - -#pragma mark - Lifecycle - -- (instancetype)init -{ - if (!(self = [super init])) { - return nil; - } - - _isMutable = YES; - _primitiveTraitCollection = ASPrimitiveTraitCollectionMakeDefault(); - _childrenArray = [[NSMutableArray alloc] init]; - - return self; -} - -- (ASLayoutElementType)layoutElementType -{ - return ASLayoutElementTypeLayoutSpec; -} - -- (BOOL)canLayoutAsynchronous -{ - return YES; -} - -- (BOOL)implementsLayoutMethod -{ - return YES; -} - -#pragma mark - Style - -- (ASLayoutElementStyle *)style -{ - AS::MutexLocker l(__instanceLock__); - if (_style == nil) { - _style = [[ASLayoutElementStyle alloc] init]; - } - return _style; -} - -- (instancetype)styledWithBlock:(AS_NOESCAPE void (^)(__kindof ASLayoutElementStyle *style))styleBlock -{ - styleBlock(self.style); - return self; -} - -#pragma mark - Layout - -ASLayoutElementLayoutCalculationDefaults - -- (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize -{ - return [ASLayout layoutWithLayoutElement:self size:constrainedSize.min]; -} - -#pragma mark - Child - -- (void)setChild:(id)child -{ - ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable"); - ASDisplayNodeAssert(_childrenArray.count < 2, @"This layout spec does not support more than one child. Use the setChildren: or the setChild:AtIndex: API"); - - if (child) { - _childrenArray[0] = child; - } else { - if (_childrenArray.count) { - [_childrenArray removeObjectAtIndex:0]; - } - } -} - -- (id)child -{ - ASDisplayNodeAssert(_childrenArray.count < 2, @"This layout spec does not support more than one child. Use the setChildren: or the setChild:AtIndex: API"); - - return _childrenArray.firstObject; -} - -#pragma mark - Children - -- (void)setChildren:(NSArray> *)children -{ - ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable"); - -#if ASDISPLAYNODE_ASSERTIONS_ENABLED - for (id child in children) { - ASDisplayNodeAssert([child conformsToProtocol:NSProtocolFromString(@"ASLayoutElement")], @"Child %@ of spec %@ is not an ASLayoutElement!", child, self); - } -#endif - [_childrenArray setArray:children]; -} - -- (nullable NSArray> *)children -{ - return [_childrenArray copy]; -} - -- (NSArray> *)sublayoutElements -{ - return [_childrenArray copy]; -} - -#pragma mark - NSFastEnumeration - -- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id __unsafe_unretained _Nullable [_Nonnull])buffer count:(NSUInteger)len -{ - return [_childrenArray countByEnumeratingWithState:state objects:buffer count:len]; -} - -#pragma mark - ASTraitEnvironment - -- (ASTraitCollection *)asyncTraitCollection -{ - AS::MutexLocker l(__instanceLock__); - return [ASTraitCollection traitCollectionWithASPrimitiveTraitCollection:self.primitiveTraitCollection]; -} - -ASPrimitiveTraitCollectionDefaults - -#pragma mark - ASLayoutElementStyleExtensibility - -ASLayoutElementStyleExtensibilityForwarding - -#pragma mark - ASDescriptionProvider - -- (NSMutableArray *)propertiesForDescription -{ - const auto result = [NSMutableArray array]; - if (NSArray *children = self.children) { - // Use tiny descriptions because these trees can get nested very deep. - const auto tinyDescriptions = ASArrayByFlatMapping(children, id object, ASObjectDescriptionMakeTiny(object)); - [result addObject:@{ @"children": tinyDescriptions }]; - } - return result; -} - -- (NSString *)description -{ - return ASObjectDescriptionMake(self, [self propertiesForDescription]); -} - -#pragma mark - Framework Private - -#if AS_DEDUPE_LAYOUT_SPEC_TREE -- (nullable NSHashTable> *)findDuplicatedElementsInSubtree -{ - NSHashTable *result = nil; - NSUInteger count = 0; - [self _findDuplicatedElementsInSubtreeWithWorkingSet:[NSHashTable hashTableWithOptions:NSHashTableObjectPointerPersonality] workingCount:&count result:&result]; - return result; -} - -/** - * This method is extremely performance-sensitive, so we do some strange things. - * - * @param workingSet A working set of elements for use in the recursion. - * @param workingCount The current count of the set for use in the recursion. - * @param result The set into which to put the result. This initially points to @c nil to save time if no duplicates exist. - */ -- (void)_findDuplicatedElementsInSubtreeWithWorkingSet:(NSHashTable> *)workingSet workingCount:(NSUInteger *)workingCount result:(NSHashTable> * _Nullable *)result -{ - Class layoutSpecClass = [ASLayoutSpec class]; - - for (id child in self) { - // Add the object into the set. - [workingSet addObject:child]; - - // Check that addObject: caused the count to increase. - // This is faster than using containsObject. - NSUInteger oldCount = *workingCount; - NSUInteger newCount = workingSet.count; - BOOL objectAlreadyExisted = (newCount != oldCount + 1); - if (objectAlreadyExisted) { - if (*result == nil) { - *result = [NSHashTable hashTableWithOptions:NSHashTableObjectPointerPersonality]; - } - [*result addObject:child]; - } else { - *workingCount = newCount; - // If child is a layout spec we haven't visited, recurse its children. - if ([child isKindOfClass:layoutSpecClass]) { - [(ASLayoutSpec *)child _findDuplicatedElementsInSubtreeWithWorkingSet:workingSet workingCount:workingCount result:result]; - } - } - } -} -#endif - -#pragma mark - Debugging - -- (NSString *)debugName -{ - AS::MutexLocker l(__instanceLock__); - return _debugName; -} - -- (void)setDebugName:(NSString *)debugName -{ - AS::MutexLocker l(__instanceLock__); - if (!ASObjectIsEqual(_debugName, debugName)) { - _debugName = [debugName copy]; - } -} - -#pragma mark - ASLayoutElementAsciiArtProtocol - -- (NSString *)asciiArtString -{ - NSArray *children = self.children.count < 2 && self.child ? @[self.child] : self.children; - return [ASLayoutSpec asciiArtStringForChildren:children parentName:[self asciiArtName]]; -} - -- (NSString *)asciiArtName -{ - NSMutableString *result = [NSMutableString stringWithCString:object_getClassName(self) encoding:NSASCIIStringEncoding]; - if (_debugName) { - [result appendFormat:@" (%@)", _debugName]; - } - return result; -} - -ASSynthesizeLockingMethodsWithMutex(__instanceLock__) - -@end - -#pragma mark - ASWrapperLayoutSpec - -@implementation ASWrapperLayoutSpec - -+ (instancetype)wrapperWithLayoutElement:(id)layoutElement NS_RETURNS_RETAINED -{ - return [[self alloc] initWithLayoutElement:layoutElement]; -} - -- (instancetype)initWithLayoutElement:(id)layoutElement -{ - self = [super init]; - if (self) { - self.child = layoutElement; - } - return self; -} - -+ (instancetype)wrapperWithLayoutElements:(NSArray> *)layoutElements NS_RETURNS_RETAINED -{ - return [[self alloc] initWithLayoutElements:layoutElements]; -} - -- (instancetype)initWithLayoutElements:(NSArray> *)layoutElements -{ - self = [super init]; - if (self) { - self.children = layoutElements; - } - return self; -} - -- (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize -{ - NSArray *children = self.children; - const auto count = children.count; - ASLayout *rawSublayouts[count]; - int i = 0; - - CGSize size = constrainedSize.min; - for (id child in children) { - ASLayout *sublayout = [child layoutThatFits:constrainedSize parentSize:constrainedSize.max]; - sublayout.position = CGPointZero; - - size.width = MAX(size.width, sublayout.size.width); - size.height = MAX(size.height, sublayout.size.height); - - rawSublayouts[i++] = sublayout; - } - const auto sublayouts = [NSArray arrayByTransferring:rawSublayouts count:i]; - return [ASLayout layoutWithLayoutElement:self size:size sublayouts:sublayouts]; -} - -@end - -#pragma mark - ASLayoutSpec (Debugging) - -@implementation ASLayoutSpec (Debugging) - -#pragma mark - ASCII Art Helpers - -+ (NSString *)asciiArtStringForChildren:(NSArray *)children parentName:(NSString *)parentName direction:(ASStackLayoutDirection)direction -{ - NSMutableArray *childStrings = [NSMutableArray array]; - for (id layoutChild in children) { - NSString *childString = [layoutChild asciiArtString]; - if (childString) { - [childStrings addObject:childString]; - } - } - if (direction == ASStackLayoutDirectionHorizontal) { - return [ASAsciiArtBoxCreator horizontalBoxStringForChildren:childStrings parent:parentName]; - } - return [ASAsciiArtBoxCreator verticalBoxStringForChildren:childStrings parent:parentName]; -} - -+ (NSString *)asciiArtStringForChildren:(NSArray *)children parentName:(NSString *)parentName -{ - return [self asciiArtStringForChildren:children parentName:parentName direction:ASStackLayoutDirectionHorizontal]; -} - -@end diff --git a/submodules/AsyncDisplayKit/Source/ASLayoutSpecPrivate.h b/submodules/AsyncDisplayKit/Source/ASLayoutSpecPrivate.h deleted file mode 100644 index 930232096c..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASLayoutSpecPrivate.h +++ /dev/null @@ -1,37 +0,0 @@ -// -// ASLayoutSpecPrivate.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import - -#if DEBUG - #define AS_DEDUPE_LAYOUT_SPEC_TREE 1 -#else - #define AS_DEDUPE_LAYOUT_SPEC_TREE 0 -#endif - -NS_ASSUME_NONNULL_BEGIN - -@interface ASLayoutSpec() { - AS::RecursiveMutex __instanceLock__; - std::atomic _primitiveTraitCollection; - ASLayoutElementStyle *_style; - NSMutableArray *_childrenArray; -} - -#if AS_DEDUPE_LAYOUT_SPEC_TREE -/** - * Recursively search the subtree for elements that occur more than once. - */ -- (nullable NSHashTable> *)findDuplicatedElementsInSubtree; -#endif - -@end - -NS_ASSUME_NONNULL_END diff --git a/submodules/AsyncDisplayKit/Source/ASLayoutSpecUtilities.h b/submodules/AsyncDisplayKit/Source/ASLayoutSpecUtilities.h deleted file mode 100644 index 62a3d9177b..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASLayoutSpecUtilities.h +++ /dev/null @@ -1,103 +0,0 @@ -// -// ASLayoutSpecUtilities.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#import -#import -#import -#import - -namespace AS { - // adopted from http://stackoverflow.com/questions/14945223/map-function-with-c11-constructs - // Takes an iterable, applies a function to every element, - // and returns a vector of the results - // - template - auto map(const T &iterable, Func &&func) -> std::vector()))> - { - // Some convenience type definitions - typedef decltype(func(std::declval())) value_type; - typedef std::vector result_type; - - // Prepares an output vector of the appropriate size - result_type res(iterable.size()); - - // Let std::transform apply `func` to all elements - // (use perfect forwarding for the function object) - std::transform( - begin(iterable), end(iterable), res.begin(), - std::forward(func) - ); - - return res; - } - - template - auto map(id collection, Func &&func) -> std::vector()))> - { - std::vector()))> to; - for (id obj in collection) { - to.push_back(func(obj)); - } - return to; - } - - template - auto filter(const T &iterable, Func &&func) -> std::vector - { - std::vector to; - for (auto obj : iterable) { - if (func(obj)) { - to.push_back(obj); - } - } - return to; - } -}; - -inline CGPoint operator+(const CGPoint &p1, const CGPoint &p2) -{ - return { p1.x + p2.x, p1.y + p2.y }; -} - -inline CGPoint operator-(const CGPoint &p1, const CGPoint &p2) -{ - return { p1.x - p2.x, p1.y - p2.y }; -} - -inline CGSize operator+(const CGSize &s1, const CGSize &s2) -{ - return { s1.width + s2.width, s1.height + s2.height }; -} - -inline CGSize operator-(const CGSize &s1, const CGSize &s2) -{ - return { s1.width - s2.width, s1.height - s2.height }; -} - -inline UIEdgeInsets operator+(const UIEdgeInsets &e1, const UIEdgeInsets &e2) -{ - return { e1.top + e2.top, e1.left + e2.left, e1.bottom + e2.bottom, e1.right + e2.right }; -} - -inline UIEdgeInsets operator-(const UIEdgeInsets &e1, const UIEdgeInsets &e2) -{ - return { e1.top - e2.top, e1.left - e2.left, e1.bottom - e2.bottom, e1.right - e2.right }; -} - -inline UIEdgeInsets operator*(const UIEdgeInsets &e1, const UIEdgeInsets &e2) -{ - return { e1.top * e2.top, e1.left * e2.left, e1.bottom * e2.bottom, e1.right * e2.right }; -} - -inline UIEdgeInsets operator-(const UIEdgeInsets &e) -{ - return { -e.top, -e.left, -e.bottom, -e.right }; -} diff --git a/submodules/AsyncDisplayKit/Source/ASLayoutTransition.h b/submodules/AsyncDisplayKit/Source/ASLayoutTransition.h deleted file mode 100644 index d11eb65fb1..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASLayoutTransition.h +++ /dev/null @@ -1,94 +0,0 @@ -// -// ASLayoutTransition.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import -#import -#import "ASDisplayNodeLayout.h" -#import - -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -#pragma mark - ASLayoutElementTransition - -/** - * Objects conform to this project returns if it's possible to layout asynchronous - */ -@protocol ASLayoutElementTransition - -/** - * @abstract Returns if the layoutElement can be used to layout in an asynchronous way on a background thread. - */ -@property (nonatomic, readonly) BOOL canLayoutAsynchronous; - -@end - -@interface ASDisplayNode () -@end -@interface ASLayoutSpec () -@end - - -#pragma mark - ASLayoutTransition - -AS_SUBCLASSING_RESTRICTED -@interface ASLayoutTransition : NSObject <_ASTransitionContextLayoutDelegate> - -/** - * Node to apply layout transition on - */ -@property (nonatomic, weak, readonly) ASDisplayNode *node; - -/** - * Previous layout to transition from - */ -@property (nonatomic, readonly) const ASDisplayNodeLayout &previousLayout NS_RETURNS_INNER_POINTER; - -/** - * Pending layout to transition to - */ -@property (nonatomic, readonly) const ASDisplayNodeLayout &pendingLayout NS_RETURNS_INNER_POINTER; - -/** - * Returns if the layout transition needs to happen synchronously - */ -@property (nonatomic, readonly) BOOL isSynchronous; - -/** - * Returns a newly initialized layout transition - */ -- (instancetype)initWithNode:(ASDisplayNode *)node - pendingLayout:(const ASDisplayNodeLayout &)pendingLayout - previousLayout:(const ASDisplayNodeLayout &)previousLayout NS_DESIGNATED_INITIALIZER; - -/** - * Insert and remove subnodes that were added or removed between the previousLayout and the pendingLayout - */ -- (void)commitTransition; - -/** - * Insert all new subnodes that were added and move the subnodes that moved between the previous layout and - * the pending layout. - */ -- (void)applySubnodeInsertionsAndMoves; - -/** - * Remove all subnodes that are removed between the previous layout and the pending layout - */ -- (void)applySubnodeRemovals; - -- (instancetype)init NS_UNAVAILABLE; -- (instancetype)new NS_UNAVAILABLE; - -@end - -NS_ASSUME_NONNULL_END diff --git a/submodules/AsyncDisplayKit/Source/ASLayoutTransition.mm b/submodules/AsyncDisplayKit/Source/ASLayoutTransition.mm deleted file mode 100644 index 0956943cc0..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASLayoutTransition.mm +++ /dev/null @@ -1,298 +0,0 @@ -// -// ASLayoutTransition.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import "ASLayoutTransition.h" - -#import - -#import -#import "ASDisplayNodeInternal.h" // Required for _insertSubnode... / _removeFromSupernode. - -#import - -#if AS_IG_LIST_KIT -#import -#import -#endif - -using AS::MutexLocker; - -/** - * Search the whole layout stack if at least one layout has a layoutElement object that can not be layed out asynchronous. - * This can be the case for example if a node was already loaded - */ -static inline BOOL ASLayoutCanTransitionAsynchronous(ASLayout *layout) { - // Queue used to keep track of sublayouts while traversing this layout in a BFS fashion. - std::queue queue; - queue.push(layout); - - while (!queue.empty()) { - layout = queue.front(); - queue.pop(); - -#if DEBUG - ASDisplayNodeCAssert([layout.layoutElement conformsToProtocol:@protocol(ASLayoutElementTransition)], @"ASLayoutElement in a layout transition needs to conforms to the ASLayoutElementTransition protocol."); -#endif - if (((id)layout.layoutElement).canLayoutAsynchronous == NO) { - return NO; - } - - // Add all sublayouts to process in next step - for (ASLayout *sublayout in layout.sublayouts) { - queue.push(sublayout); - } - } - - return YES; -} - -@implementation ASLayoutTransition { - std::shared_ptr __instanceLock__; - - BOOL _calculatedSubnodeOperations; - NSArray *_insertedSubnodes; - NSArray *_removedSubnodes; - std::vector _insertedSubnodePositions; - std::vector> _subnodeMoves; - ASDisplayNodeLayout _pendingLayout; - ASDisplayNodeLayout _previousLayout; -} - -- (instancetype)initWithNode:(ASDisplayNode *)node - pendingLayout:(const ASDisplayNodeLayout &)pendingLayout - previousLayout:(const ASDisplayNodeLayout &)previousLayout -{ - self = [super init]; - if (self) { - __instanceLock__ = std::make_shared(); - - _node = node; - _pendingLayout = pendingLayout; - _previousLayout = previousLayout; - } - return self; -} - -- (BOOL)isSynchronous -{ - MutexLocker l(*__instanceLock__); - return !ASLayoutCanTransitionAsynchronous(_pendingLayout.layout); -} - -- (void)commitTransition -{ - [self applySubnodeRemovals]; - [self applySubnodeInsertionsAndMoves]; -} - -- (void)applySubnodeInsertionsAndMoves -{ - MutexLocker l(*__instanceLock__); - [self calculateSubnodeOperationsIfNeeded]; - - // Create an activity even if no subnodes affected. - if (_insertedSubnodePositions.size() == 0 && _subnodeMoves.size() == 0) { - return; - } - - ASDisplayNodeLogEvent(_node, @"insertSubnodes: %@", _insertedSubnodes); - NSUInteger i = 0; - NSUInteger j = 0; - for (auto const &move : _subnodeMoves) { - [move.first _removeFromSupernodeIfEqualTo:_node]; - } - j = 0; - while (i < _insertedSubnodePositions.size() && j < _subnodeMoves.size()) { - NSUInteger p = _insertedSubnodePositions[i]; - NSUInteger q = _subnodeMoves[j].second; - if (p < q) { - [_node _insertSubnode:_insertedSubnodes[i] atIndex:p]; - i++; - } else { - [_node _insertSubnode:_subnodeMoves[j].first atIndex:q]; - j++; - } - } - for (; i < _insertedSubnodePositions.size(); ++i) { - [_node _insertSubnode:_insertedSubnodes[i] atIndex:_insertedSubnodePositions[i]]; - } - for (; j < _subnodeMoves.size(); ++j) { - [_node _insertSubnode:_subnodeMoves[j].first atIndex:_subnodeMoves[j].second]; - } -} - -- (void)applySubnodeRemovals -{ - MutexLocker l(*__instanceLock__); - [self calculateSubnodeOperationsIfNeeded]; - - if (_removedSubnodes.count == 0) { - return; - } - - ASDisplayNodeLogEvent(_node, @"removeSubnodes: %@", _removedSubnodes); - for (ASDisplayNode *subnode in _removedSubnodes) { - // In this case we should only remove the subnode if it's still a subnode of the _node that executes a layout transition. - // It can happen that a node already did a layout transition and added this subnode, in this case the subnode - // would be removed from the new node instead of _node - if (_node.automaticallyManagesSubnodes) { - [subnode _removeFromSupernodeIfEqualTo:_node]; - } - } -} - -- (void)calculateSubnodeOperationsIfNeeded -{ - MutexLocker l(*__instanceLock__); - if (_calculatedSubnodeOperations) { - return; - } - - // Create an activity even if no subnodes affected. - ASLayout *previousLayout = _previousLayout.layout; - ASLayout *pendingLayout = _pendingLayout.layout; - - if (previousLayout) { -#if AS_IG_LIST_KIT - // IGListDiff completes in linear time O(m+n), so use it if we have it: - IGListIndexSetResult *result = IGListDiff(previousLayout.sublayouts, pendingLayout.sublayouts, IGListDiffEquality); - _insertedSubnodePositions = findNodesInLayoutAtIndexes(pendingLayout, result.inserts, &_insertedSubnodes); - findNodesInLayoutAtIndexes(previousLayout, result.deletes, &_removedSubnodes); - for (IGListMoveIndex *move in result.moves) { - _subnodeMoves.push_back(std::make_pair(previousLayout.sublayouts[move.from].layoutElement, move.to)); - } - - // Sort by ascending order of move destinations, this will allow easy loop of `insertSubnode:AtIndex` later. - std::sort(_subnodeMoves.begin(), _subnodeMoves.end(), [](std::pair, NSUInteger> a, - std::pair b) { - return a.second < b.second; - }); -#else - NSIndexSet *insertions, *deletions; - NSArray *moves; - NSArray *previousNodes = [previousLayout.sublayouts valueForKey:@"layoutElement"]; - NSArray *pendingNodes = [pendingLayout.sublayouts valueForKey:@"layoutElement"]; - [previousNodes asdk_diffWithArray:pendingNodes - insertions:&insertions - deletions:&deletions - moves:&moves]; - - _insertedSubnodePositions = findNodesInLayoutAtIndexes(pendingLayout, insertions, &_insertedSubnodes); - _removedSubnodes = [previousNodes objectsAtIndexes:deletions]; - // These should arrive sorted in ascending order of move destinations. - for (NSIndexPath *move in moves) { - _subnodeMoves.push_back(std::make_pair(previousLayout.sublayouts[([move indexAtPosition:0])].layoutElement, - [move indexAtPosition:1])); - } -#endif - } else { - NSIndexSet *indexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [pendingLayout.sublayouts count])]; - _insertedSubnodePositions = findNodesInLayoutAtIndexes(pendingLayout, indexes, &_insertedSubnodes); - _removedSubnodes = nil; - } - _calculatedSubnodeOperations = YES; -} - -#pragma mark - _ASTransitionContextDelegate - -- (NSArray *)currentSubnodesWithTransitionContext:(_ASTransitionContext *)context -{ - MutexLocker l(*__instanceLock__); - return _node.subnodes; -} - -- (NSArray *)insertedSubnodesWithTransitionContext:(_ASTransitionContext *)context -{ - MutexLocker l(*__instanceLock__); - [self calculateSubnodeOperationsIfNeeded]; - return _insertedSubnodes; -} - -- (NSArray *)removedSubnodesWithTransitionContext:(_ASTransitionContext *)context -{ - MutexLocker l(*__instanceLock__); - [self calculateSubnodeOperationsIfNeeded]; - return _removedSubnodes; -} - -- (ASLayout *)transitionContext:(_ASTransitionContext *)context layoutForKey:(NSString *)key -{ - MutexLocker l(*__instanceLock__); - if ([key isEqualToString:ASTransitionContextFromLayoutKey]) { - return _previousLayout.layout; - } else if ([key isEqualToString:ASTransitionContextToLayoutKey]) { - return _pendingLayout.layout; - } else { - return nil; - } -} - -- (ASSizeRange)transitionContext:(_ASTransitionContext *)context constrainedSizeForKey:(NSString *)key -{ - MutexLocker l(*__instanceLock__); - if ([key isEqualToString:ASTransitionContextFromLayoutKey]) { - return _previousLayout.constrainedSize; - } else if ([key isEqualToString:ASTransitionContextToLayoutKey]) { - return _pendingLayout.constrainedSize; - } else { - return ASSizeRangeMake(CGSizeZero, CGSizeZero); - } -} - -#pragma mark - Filter helpers - -/** - * @abstract Stores the nodes at the given indexes in the `storedNodes` array, storing indexes in a `storedPositions` c++ vector. - */ -static inline std::vector findNodesInLayoutAtIndexes(ASLayout *layout, - NSIndexSet *indexes, - NSArray * __strong *storedNodes) -{ - return findNodesInLayoutAtIndexesWithFilteredNodes(layout, indexes, nil, storedNodes); -} - -/** - * @abstract Stores the nodes at the given indexes in the `storedNodes` array, storing indexes in a `storedPositions` c++ vector. - * Call only with a flattened layout. - * @discussion If the node exists in the `filteredNodes` array, the node is not added to `storedNodes`. - */ -static inline std::vector findNodesInLayoutAtIndexesWithFilteredNodes(ASLayout *layout, - NSIndexSet *indexes, - NSArray *filteredNodes, - NSArray * __strong *storedNodes) -{ - NSMutableArray *nodes = [NSMutableArray arrayWithCapacity:indexes.count]; - std::vector positions = std::vector(); - - // From inspection, this is how enumerateObjectsAtIndexes: works under the hood - NSUInteger firstIndex = indexes.firstIndex; - NSUInteger lastIndex = indexes.lastIndex; - NSUInteger idx = 0; - for (ASLayout *sublayout in layout.sublayouts) { - if (idx > lastIndex) { break; } - if (idx >= firstIndex && [indexes containsIndex:idx]) { - ASDisplayNode *node = (ASDisplayNode *)(sublayout.layoutElement); - ASDisplayNodeCAssert(node, @"ASDisplayNode was deallocated before it was added to a subnode. It's likely the case that you use automatically manages subnodes and allocate a ASDisplayNode in layoutSpecThatFits: and don't have any strong reference to it."); - ASDisplayNodeCAssert([node isKindOfClass:[ASDisplayNode class]], @"sublayout is an ASLayout, but not an ASDisplayNode - only call findNodesInLayoutAtIndexesWithFilteredNodes with a flattened layout (all sublayouts are ASDisplayNodes)."); - if (node != nil) { - BOOL notFiltered = (filteredNodes == nil || [filteredNodes indexOfObjectIdenticalTo:node] == NSNotFound); - if (notFiltered) { - [nodes addObject:node]; - positions.push_back(idx); - } - } - } - idx += 1; - } - *storedNodes = nodes; - - return positions; -} - -@end diff --git a/submodules/AsyncDisplayKit/Source/ASMainSerialQueue.h b/submodules/AsyncDisplayKit/Source/ASMainSerialQueue.h deleted file mode 100644 index 405164f75d..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASMainSerialQueue.h +++ /dev/null @@ -1,19 +0,0 @@ -// -// ASMainSerialQueue.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import - -AS_SUBCLASSING_RESTRICTED -@interface ASMainSerialQueue : NSObject - -@property (nonatomic, readonly) NSUInteger numberOfScheduledBlocks; -- (void)performBlockOnMainThread:(dispatch_block_t)block; - -@end diff --git a/submodules/AsyncDisplayKit/Source/ASMainSerialQueue.mm b/submodules/AsyncDisplayKit/Source/ASMainSerialQueue.mm deleted file mode 100644 index 60bf8d7f67..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASMainSerialQueue.mm +++ /dev/null @@ -1,81 +0,0 @@ -// -// ASMainSerialQueue.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import "ASMainSerialQueue.h" - -#import -#import - -@interface ASMainSerialQueue () -{ - AS::Mutex _serialQueueLock; - NSMutableArray *_blocks; -} - -@end - -@implementation ASMainSerialQueue - -- (instancetype)init -{ - if (!(self = [super init])) { - return nil; - } - - _blocks = [[NSMutableArray alloc] init]; - return self; -} - -- (NSUInteger)numberOfScheduledBlocks -{ - AS::MutexLocker l(_serialQueueLock); - return _blocks.count; -} - -- (void)performBlockOnMainThread:(dispatch_block_t)block -{ - - AS::UniqueLock l(_serialQueueLock); - [_blocks addObject:block]; - { - l.unlock(); - [self runBlocks]; - l.lock(); - } -} - -- (void)runBlocks -{ - dispatch_block_t mainThread = ^{ - AS::UniqueLock l(self->_serialQueueLock); - do { - dispatch_block_t block; - if (self->_blocks.count > 0) { - block = _blocks[0]; - [self->_blocks removeObjectAtIndex:0]; - } else { - break; - } - { - l.unlock(); - block(); - l.lock(); - } - } while (true); - }; - - ASPerformBlockOnMainThread(mainThread); -} - -- (NSString *)description -{ - return [[super description] stringByAppendingFormat:@" Blocks: %@", _blocks]; -} - -@end diff --git a/submodules/AsyncDisplayKit/Source/ASMainThreadDeallocation.mm b/submodules/AsyncDisplayKit/Source/ASMainThreadDeallocation.mm deleted file mode 100644 index 4b16c932d2..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASMainThreadDeallocation.mm +++ /dev/null @@ -1,199 +0,0 @@ -// -// ASMainThreadDeallocation.mm -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#import -#import -#import -#import - -#import -#import - -@implementation NSObject (ASMainThreadIvarTeardown) - -- (void)scheduleIvarsForMainThreadDeallocation -{ - if (ASDisplayNodeThreadIsMain()) { - return; - } - - NSValue *ivarsObj = [[self class] _ivarsThatMayNeedMainDeallocation]; - - // Unwrap the ivar array - unsigned int count = 0; - // Will be unused if assertions are disabled. - __unused int scanResult = sscanf(ivarsObj.objCType, "[%u^{objc_ivar}]", &count); - ASDisplayNodeAssert(scanResult == 1, @"Unexpected type in NSValue: %s", ivarsObj.objCType); - Ivar ivars[count]; - [ivarsObj getValue:ivars]; - - for (Ivar ivar : ivars) { - id value = object_getIvar(self, ivar); - if (value == nil) { - continue; - } - - if ([object_getClass(value) needsMainThreadDeallocation]) { - // Release the ivar's reference before handing the object to the queue so we - // don't risk holding onto it longer than the queue does. - object_setIvar(self, ivar, nil); - - ASPerformMainThreadDeallocation(&value); - } else { - } - } -} - -/** - * Returns an NSValue-wrapped array of all the ivars in this class or its superclasses - * up through ASDisplayNode, that we expect may need to be deallocated on main. - * - * This method caches its results. - * - * Result is of type NSValue<[Ivar]> - */ -+ (NSValue * _Nonnull)_ivarsThatMayNeedMainDeallocation NS_RETURNS_RETAINED -{ - static NSCache *ivarsCache; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - ivarsCache = [[NSCache alloc] init]; - }); - - NSValue *result = [ivarsCache objectForKey:self]; - if (result != nil) { - return result; - } - - // Cache miss. - unsigned int resultCount = 0; - static const int kMaxDealloc2MainIvarsPerClassTree = 64; - Ivar resultIvars[kMaxDealloc2MainIvarsPerClassTree]; - - // Get superclass results first. - Class c = class_getSuperclass(self); - if (c != [NSObject class]) { - NSValue *ivarsObj = [c _ivarsThatMayNeedMainDeallocation]; - // Unwrap the ivar array and append it to our working array - unsigned int count = 0; - // Will be unused if assertions are disabled. - __unused int scanResult = sscanf(ivarsObj.objCType, "[%u^{objc_ivar}]", &count); - ASDisplayNodeAssert(scanResult == 1, @"Unexpected type in NSValue: %s", ivarsObj.objCType); - ASDisplayNodeCAssert(resultCount + count < kMaxDealloc2MainIvarsPerClassTree, @"More than %d dealloc2main ivars are not supported. Count: %d", kMaxDealloc2MainIvarsPerClassTree, resultCount + count); - [ivarsObj getValue:resultIvars + resultCount]; - resultCount += count; - } - - // Now gather ivars from this particular class. - unsigned int allMyIvarsCount; - Ivar *allMyIvars = class_copyIvarList(self, &allMyIvarsCount); - - for (NSUInteger i = 0; i < allMyIvarsCount; i++) { - Ivar ivar = allMyIvars[i]; - - // NOTE: Would be great to exclude weak/unowned ivars, since we don't - // release them. Unfortunately the objc_ivar_management access is private and - // class_getWeakIvarLayout does not have a well-defined structure. - - const char *type = ivar_getTypeEncoding(ivar); - - if (type != NULL && strcmp(type, @encode(id)) == 0) { - // If it's `id` we have to include it just in case. - resultIvars[resultCount] = ivar; - resultCount += 1; - } else { - // If it's an ivar with a static type, check the type. - Class c = ASGetClassFromType(type); - if ([c needsMainThreadDeallocation]) { - resultIvars[resultCount] = ivar; - resultCount += 1; - } else { - } - } - } - free(allMyIvars); - - // Encode the type (array of Ivars) into a string and wrap it in an NSValue - char arrayType[32]; - snprintf(arrayType, 32, "[%u^{objc_ivar}]", resultCount); - result = [NSValue valueWithBytes:resultIvars objCType:arrayType]; - - [ivarsCache setObject:result forKey:self]; - return result; -} - -@end - -@implementation NSObject (ASNeedsMainThreadDeallocation) - -+ (BOOL)needsMainThreadDeallocation -{ - const auto name = class_getName(self); - if (0 == strncmp(name, "AV", 2) || 0 == strncmp(name, "UI", 2) || 0 == strncmp(name, "CA", 2)) { - return YES; - } - return NO; -} - -@end - -@implementation CALayer (ASNeedsMainThreadDeallocation) - -+ (BOOL)needsMainThreadDeallocation -{ - return YES; -} - -@end - -@implementation UIColor (ASNeedsMainThreadDeallocation) - -+ (BOOL)needsMainThreadDeallocation -{ - return NO; -} - -@end - -@implementation UIGestureRecognizer (ASNeedsMainThreadDeallocation) - -+ (BOOL)needsMainThreadDeallocation -{ - return YES; -} - -@end - -@implementation UIImage (ASNeedsMainThreadDeallocation) - -+ (BOOL)needsMainThreadDeallocation -{ - return NO; -} - -@end - -@implementation UIResponder (ASNeedsMainThreadDeallocation) - -+ (BOOL)needsMainThreadDeallocation -{ - return YES; -} - -@end - -@implementation NSProxy (ASNeedsMainThreadDeallocation) - -+ (BOOL)needsMainThreadDeallocation -{ - return NO; -} - -@end diff --git a/submodules/AsyncDisplayKit/Source/ASObjectDescriptionHelpers.mm b/submodules/AsyncDisplayKit/Source/ASObjectDescriptionHelpers.mm deleted file mode 100644 index 2b5d94492c..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASObjectDescriptionHelpers.mm +++ /dev/null @@ -1,101 +0,0 @@ -// -// ASObjectDescriptionHelpers.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#import - -#import "NSIndexSet+ASHelpers.h" - -NSString *ASGetDescriptionValueString(id object) -{ - if ([object isKindOfClass:[NSValue class]]) { - // Use shortened NSValue descriptions - NSValue *value = object; - const char *type = value.objCType; - - if (strcmp(type, @encode(CGRect)) == 0) { - CGRect rect = [value CGRectValue]; - return [NSString stringWithFormat:@"(%g %g; %g %g)", rect.origin.x, rect.origin.y, rect.size.width, rect.size.height]; - } else if (strcmp(type, @encode(CGSize)) == 0) { - return NSStringFromCGSize(value.CGSizeValue); - } else if (strcmp(type, @encode(CGPoint)) == 0) { - return NSStringFromCGPoint(value.CGPointValue); - } - - } else if ([object isKindOfClass:[NSIndexSet class]]) { - return [object as_smallDescription]; - } else if ([object isKindOfClass:[NSIndexPath class]]) { - // index paths like (0, 7) - NSIndexPath *indexPath = object; - NSMutableArray *strings = [NSMutableArray array]; - for (NSUInteger i = 0; i < indexPath.length; i++) { - [strings addObject:[NSString stringWithFormat:@"%lu", (unsigned long)[indexPath indexAtPosition:i]]]; - } - return [NSString stringWithFormat:@"(%@)", [strings componentsJoinedByString:@", "]]; - } else if ([object respondsToSelector:@selector(componentsJoinedByString:)]) { - // e.g. "[ ]" - return [NSString stringWithFormat:@"[ %@ ]", [object componentsJoinedByString:@" "]]; - } - return [object description]; -} - -NSString *_ASObjectDescriptionMakePropertyList(NSArray * _Nullable propertyGroups) -{ - NSMutableArray *components = [NSMutableArray array]; - for (NSDictionary *properties in propertyGroups) { - [properties enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) { - NSString *str; - if (key == (id)kCFNull) { - str = ASGetDescriptionValueString(obj); - } else { - str = [NSString stringWithFormat:@"%@ = %@", key, ASGetDescriptionValueString(obj)]; - } - [components addObject:str]; - }]; - } - return [components componentsJoinedByString:@"; "]; -} - -NSString *ASObjectDescriptionMakeWithoutObject(NSArray * _Nullable propertyGroups) -{ - return [NSString stringWithFormat:@"{ %@ }", _ASObjectDescriptionMakePropertyList(propertyGroups)]; -} - -NSString *ASObjectDescriptionMake(__autoreleasing id object, NSArray *propertyGroups) -{ - if (object == nil) { - return @"(null)"; - } - - NSMutableString *str = [NSMutableString stringWithFormat:@"<%s: %p", object_getClassName(object), object]; - - NSString *propList = _ASObjectDescriptionMakePropertyList(propertyGroups); - if (propList.length > 0) { - [str appendFormat:@"; %@", propList]; - } - [str appendString:@">"]; - return str; -} - -NSString *ASObjectDescriptionMakeTiny(__autoreleasing id object) { - return ASObjectDescriptionMake(object, nil); -} - -NSString *ASStringWithQuotesIfMultiword(NSString *string) { - if (string == nil) { - return nil; - } - - if ([string rangeOfCharacterFromSet:[NSCharacterSet whitespaceCharacterSet]].location != NSNotFound) { - return [NSString stringWithFormat:@"\"%@\"", string]; - } else { - return string; - } -} diff --git a/submodules/AsyncDisplayKit/Source/ASPendingStateController.h b/submodules/AsyncDisplayKit/Source/ASPendingStateController.h deleted file mode 100644 index b50a7c1f94..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASPendingStateController.h +++ /dev/null @@ -1,50 +0,0 @@ -// -// ASPendingStateController.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import - -@class ASDisplayNode; - -NS_ASSUME_NONNULL_BEGIN - -/** - A singleton that is responsible for applying changes to - UIView/CALayer properties of display nodes when they - have been set on background threads. - - This controller will enqueue run-loop events to flush changes - but if you need them flushed now you can call `flush` from the main thread. - */ -AS_SUBCLASSING_RESTRICTED -@interface ASPendingStateController : NSObject - -+ (ASPendingStateController *)sharedInstance; - -@property (nonatomic, readonly) BOOL hasChanges; - -/** - Flush all pending states for nodes now. Any UIView/CALayer properties - that have been set in the background will be applied to their - corresponding views/layers before this method returns. - - You must call this method on the main thread. - */ -- (void)flush; - -/** - Register this node as having pending state that needs to be copied - over to the view/layer. This is called automatically by display nodes - when their view/layer properties are set post-load on background threads. - */ -- (void)registerNode:(ASDisplayNode *)node; - -@end - -NS_ASSUME_NONNULL_END diff --git a/submodules/AsyncDisplayKit/Source/ASPendingStateController.mm b/submodules/AsyncDisplayKit/Source/ASPendingStateController.mm deleted file mode 100644 index e7ca4a71de..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASPendingStateController.mm +++ /dev/null @@ -1,102 +0,0 @@ -// -// ASPendingStateController.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import "ASPendingStateController.h" -#import -#import -#import "ASDisplayNodeInternal.h" // Required for -applyPendingViewState; consider moving this to +FrameworkPrivate - -@interface ASPendingStateController() -{ - AS::Mutex _lock; - - struct ASPendingStateControllerFlags { - unsigned pendingFlush:1; - } _flags; -} - -@property (nonatomic, readonly) ASWeakSet *dirtyNodes; -@end - -@implementation ASPendingStateController - -#pragma mark Lifecycle & Singleton - -- (instancetype)init -{ - self = [super init]; - if (self) { - _dirtyNodes = [[ASWeakSet alloc] init]; - } - return self; -} - -+ (ASPendingStateController *)sharedInstance -{ - static dispatch_once_t onceToken; - static ASPendingStateController *controller = nil; - dispatch_once(&onceToken, ^{ - controller = [[ASPendingStateController alloc] init]; - }); - return controller; -} - -#pragma mark External API - -- (void)registerNode:(ASDisplayNode *)node -{ - ASDisplayNodeAssert(node.nodeLoaded, @"Expected display node to be loaded before it was registered with ASPendingStateController. Node: %@", node); - AS::MutexLocker l(_lock); - [_dirtyNodes addObject:node]; - - [self scheduleFlushIfNeeded]; -} - -- (void)flush -{ - ASDisplayNodeAssertMainThread(); - _lock.lock(); - ASWeakSet *dirtyNodes = _dirtyNodes; - _dirtyNodes = [[ASWeakSet alloc] init]; - _flags.pendingFlush = NO; - _lock.unlock(); - - for (ASDisplayNode *node in dirtyNodes) { - [node applyPendingViewState]; - } -} - - -#pragma mark Private Methods - -/** - This method is assumed to be called with the lock held. - */ -- (void)scheduleFlushIfNeeded -{ - if (_flags.pendingFlush) { - return; - } - - _flags.pendingFlush = YES; - dispatch_async(dispatch_get_main_queue(), ^{ - [self flush]; - }); -} - -@end - -@implementation ASPendingStateController (Testing) - -- (BOOL)test_isFlushScheduled -{ - return _flags.pendingFlush; -} - -@end diff --git a/submodules/AsyncDisplayKit/Source/ASRecursiveUnfairLock.mm b/submodules/AsyncDisplayKit/Source/ASRecursiveUnfairLock.mm deleted file mode 100644 index e44eec76d1..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASRecursiveUnfairLock.mm +++ /dev/null @@ -1,83 +0,0 @@ -// -// ASRecursiveUnfairLock.mm -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#import - -/** - * For our atomic _thread, we use acquire/release memory order so that we can have - * the minimum possible constraint on the hardware. The default, `memory_order_seq_cst` - * demands that there be a total order of all such modifications as seen by all threads. - * Acquire/release only requires that modifications to this specific atomic are - * synchronized across acquire/release pairs. - * http://en.cppreference.com/w/cpp/atomic/memory_order - * - * Note also that the unfair_lock involves a thread fence as well, so we don't need to - * take care of synchronizing other values. Just the thread value. - */ -#define rul_set_thread(l, t) atomic_store_explicit(&l->_thread, t, memory_order_release) -#define rul_get_thread(l) atomic_load_explicit(&l->_thread, memory_order_acquire) - -void ASRecursiveUnfairLockLock(ASRecursiveUnfairLock *l) -{ - // Try to lock without blocking. If we fail, check what thread owns it. - // Note that the owning thread CAN CHANGE freely, but it can't become `self` - // because only we are `self`. And if it's already `self` then we already have - // the lock, because we reset it to NULL before we unlock. So (thread == self) is - // invariant. - - const pthread_t s = pthread_self(); - if (os_unfair_lock_trylock(&l->_lock)) { - // Owned by nobody. We now have the lock. Assign self. - rul_set_thread(l, s); - } else if (rul_get_thread(l) == s) { - // Owned by self (recursive lock). nop. - } else { - // Owned by other thread. Block and then set thread to self. - os_unfair_lock_lock(&l->_lock); - rul_set_thread(l, s); - } - - l->_count++; -} - -BOOL ASRecursiveUnfairLockTryLock(ASRecursiveUnfairLock *l) -{ - // Same as Lock above. See comments there. - - const pthread_t s = pthread_self(); - if (os_unfair_lock_trylock(&l->_lock)) { - // Owned by nobody. We now have the lock. Assign self. - rul_set_thread(l, s); - } else if (rul_get_thread(l) == s) { - // Owned by self (recursive lock). nop. - } else { - // Owned by other thread. Fail. - return NO; - } - - l->_count++; - return YES; -} - -void ASRecursiveUnfairLockUnlock(ASRecursiveUnfairLock *l) -{ - // Ensure we have the lock. This check may miss some pathological cases, - // but it'll catch 99.999999% of this serious programmer error. - NSCAssert(rul_get_thread(l) == pthread_self(), @"Unlocking from a different thread than locked."); - - if (0 == --l->_count) { - // Note that we have to clear this before unlocking because, if another thread - // succeeds in locking above, but hasn't managed to update _thread, and we - // try to re-lock, and fail the -tryLock, and read _thread, then we'll mistakenly - // think that we still own the lock and proceed without blocking. - rul_set_thread(l, NULL); - os_unfair_lock_unlock(&l->_lock); - } -} diff --git a/submodules/AsyncDisplayKit/Source/ASResponderChainEnumerator.h b/submodules/AsyncDisplayKit/Source/ASResponderChainEnumerator.h deleted file mode 100644 index dcd2e1c4c4..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASResponderChainEnumerator.h +++ /dev/null @@ -1,29 +0,0 @@ -// -// ASResponderChainEnumerator.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -AS_SUBCLASSING_RESTRICTED -@interface ASResponderChainEnumerator : NSEnumerator - -- (instancetype)initWithResponder:(UIResponder *)responder; - -@end - -@interface UIResponder (ASResponderChainEnumerator) - -- (ASResponderChainEnumerator *)asdk_responderChainEnumerator; - -@end - - -NS_ASSUME_NONNULL_END diff --git a/submodules/AsyncDisplayKit/Source/ASResponderChainEnumerator.mm b/submodules/AsyncDisplayKit/Source/ASResponderChainEnumerator.mm deleted file mode 100644 index 2d94c99945..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASResponderChainEnumerator.mm +++ /dev/null @@ -1,45 +0,0 @@ -// -// ASResponderChainEnumerator.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import "ASResponderChainEnumerator.h" -#import - -@implementation ASResponderChainEnumerator { - UIResponder *_currentResponder; -} - -- (instancetype)initWithResponder:(UIResponder *)responder -{ - ASDisplayNodeAssertMainThread(); - if (self = [super init]) { - _currentResponder = responder; - } - return self; -} - -#pragma mark - NSEnumerator - -- (id)nextObject -{ - ASDisplayNodeAssertMainThread(); - id result = [_currentResponder nextResponder]; - _currentResponder = result; - return result; -} - -@end - -@implementation UIResponder (ASResponderChainEnumerator) - -- (NSEnumerator *)asdk_responderChainEnumerator -{ - return [[ASResponderChainEnumerator alloc] initWithResponder:self]; -} - -@end diff --git a/submodules/AsyncDisplayKit/Source/ASRunLoopQueue.mm b/submodules/AsyncDisplayKit/Source/ASRunLoopQueue.mm deleted file mode 100644 index 2d2415e2b1..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASRunLoopQueue.mm +++ /dev/null @@ -1,464 +0,0 @@ -// -// ASRunLoopQueue.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import -#import -#import -#import -#import "ASSignpost.h" -#import -#import -#import -#import - -#define ASRunLoopQueueLoggingEnabled 0 -#define ASRunLoopQueueVerboseLoggingEnabled 0 - -using AS::MutexLocker; - -static void runLoopSourceCallback(void *info) { - // No-op -#if ASRunLoopQueueVerboseLoggingEnabled - NSLog(@"<%@> - Called runLoopSourceCallback", info); -#endif -} - -#pragma mark - ASDeallocQueue - -@implementation ASDeallocQueue { - std::vector _queue; - AS::Mutex _lock; -} - -+ (ASDeallocQueue *)sharedDeallocationQueue NS_RETURNS_RETAINED -{ - static ASDeallocQueue *deallocQueue = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - deallocQueue = [[ASDeallocQueue alloc] init]; - }); - return deallocQueue; -} - -- (void)dealloc -{ - ASDisplayNodeFailAssert(@"Singleton should not dealloc."); -} - -- (void)releaseObjectInBackground:(id _Nullable __strong *)objectPtr -{ - NSParameterAssert(objectPtr != NULL); - - // Cast to CFType so we can manipulate retain count manually. - const auto cfPtr = (CFTypeRef *)(void *)objectPtr; - if (!cfPtr || !*cfPtr) { - return; - } - - _lock.lock(); - const auto isFirstEntry = _queue.empty(); - // Push the pointer into our queue and clear their pointer. - // This "steals" the +1 from ARC and nils their pointer so they can't - // access or release the object. - _queue.push_back(*cfPtr); - *cfPtr = NULL; - _lock.unlock(); - - if (isFirstEntry) { - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.100 * NSEC_PER_SEC)), dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0), ^{ - [self drain]; - }); - } -} - -- (void)drain -{ - _lock.lock(); - const auto q = std::move(_queue); - _lock.unlock(); - for (CFTypeRef ref : q) { - // NOTE: Could check that retain count is 1 and retry later if not. - CFRelease(ref); - } -} - -@end - -@implementation ASAbstractRunLoopQueue - -- (instancetype)init -{ - self = [super init]; - if (self == nil) { - return nil; - } - ASDisplayNodeAssert(self.class != [ASAbstractRunLoopQueue class], @"Should never create instances of abstract class ASAbstractRunLoopQueue."); - return self; -} - -@end - -#pragma mark - ASRunLoopQueue - -@interface ASRunLoopQueue () { - CFRunLoopRef _runLoop; - CFRunLoopSourceRef _runLoopSource; - CFRunLoopObserverRef _runLoopObserver; - NSPointerArray *_internalQueue; // Use NSPointerArray so we can decide __strong or __weak per-instance. - AS::RecursiveMutex _internalQueueLock; - -#if ASRunLoopQueueLoggingEnabled - NSTimer *_runloopQueueLoggingTimer; -#endif -} - -@property (nonatomic) void (^queueConsumer)(id dequeuedItem, BOOL isQueueDrained); - -@end - -@implementation ASRunLoopQueue - -- (instancetype)initWithRunLoop:(CFRunLoopRef)runloop retainObjects:(BOOL)retainsObjects handler:(void (^)(id _Nullable, BOOL))handlerBlock -{ - if (self = [super init]) { - _runLoop = runloop; - NSPointerFunctionsOptions options = retainsObjects ? NSPointerFunctionsStrongMemory : NSPointerFunctionsWeakMemory; - _internalQueue = [[NSPointerArray alloc] initWithOptions:options]; - _queueConsumer = handlerBlock; - _batchSize = 1; - _ensureExclusiveMembership = YES; - - // Self is guaranteed to outlive the observer. Without the high cost of a weak pointer, - // __unsafe_unretained allows us to avoid flagging the memory cycle detector. - __unsafe_unretained __typeof__(self) weakSelf = self; - void (^handlerBlock) (CFRunLoopObserverRef observer, CFRunLoopActivity activity) = ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) { - [weakSelf processQueue]; - }; - _runLoopObserver = CFRunLoopObserverCreateWithHandler(NULL, kCFRunLoopBeforeWaiting, true, 0, handlerBlock); - CFRunLoopAddObserver(_runLoop, _runLoopObserver, kCFRunLoopCommonModes); - - // It is not guaranteed that the runloop will turn if it has no scheduled work, and this causes processing of - // the queue to stop. Attaching a custom loop source to the run loop and signal it if new work needs to be done - CFRunLoopSourceContext sourceContext = {}; - sourceContext.perform = runLoopSourceCallback; -#if ASRunLoopQueueLoggingEnabled - sourceContext.info = (__bridge void *)self; -#endif - _runLoopSource = CFRunLoopSourceCreate(NULL, 0, &sourceContext); - CFRunLoopAddSource(runloop, _runLoopSource, kCFRunLoopCommonModes); - -#if ASRunLoopQueueLoggingEnabled - _runloopQueueLoggingTimer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(checkRunLoop) userInfo:nil repeats:YES]; - [[NSRunLoop mainRunLoop] addTimer:_runloopQueueLoggingTimer forMode:NSRunLoopCommonModes]; -#endif - } - return self; -} - -- (void)dealloc -{ - if (CFRunLoopContainsSource(_runLoop, _runLoopSource, kCFRunLoopCommonModes)) { - CFRunLoopRemoveSource(_runLoop, _runLoopSource, kCFRunLoopCommonModes); - } - CFRelease(_runLoopSource); - _runLoopSource = nil; - - if (CFRunLoopObserverIsValid(_runLoopObserver)) { - CFRunLoopObserverInvalidate(_runLoopObserver); - } - CFRelease(_runLoopObserver); - _runLoopObserver = nil; -} - -#if ASRunLoopQueueLoggingEnabled -- (void)checkRunLoop -{ - NSLog(@"<%@> - Jobs: %ld", self, _internalQueue.count); -} -#endif - -- (void)processQueue -{ - BOOL hasExecutionBlock = (_queueConsumer != nil); - - // If we have an execution block, this vector will be populated, otherwise remains empty. - // This is to avoid needlessly retaining/releasing the objects if we don't have a block. - std::vector itemsToProcess; - - BOOL isQueueDrained = NO; - { - MutexLocker l(_internalQueueLock); - - NSInteger internalQueueCount = _internalQueue.count; - // Early-exit if the queue is empty. - if (internalQueueCount == 0) { - return; - } - - ASSignpostStart(ASSignpostRunLoopQueueBatch); - - // Snatch the next batch of items. - NSInteger maxCountToProcess = MIN(internalQueueCount, self.batchSize); - - /** - * For each item in the next batch, if it's non-nil then NULL it out - * and if we have an execution block then add it in. - * This could be written a bunch of different ways but - * this particular one nicely balances readability, safety, and efficiency. - */ - NSInteger foundItemCount = 0; - for (NSInteger i = 0; i < internalQueueCount && foundItemCount < maxCountToProcess; i++) { - /** - * It is safe to use unsafe_unretained here. If the queue is weak, the - * object will be added to the autorelease pool. If the queue is strong, - * it will retain the object until we transfer it (retain it) in itemsToProcess. - */ - __unsafe_unretained id ptr = (__bridge id)[_internalQueue pointerAtIndex:i]; - if (ptr != nil) { - foundItemCount++; - if (hasExecutionBlock) { - itemsToProcess.push_back(ptr); - } - [_internalQueue replacePointerAtIndex:i withPointer:NULL]; - } - } - - if (foundItemCount == 0) { - // If _internalQueue holds weak references, and all of them just become NULL, then the array - // is never marked as needsCompletion, and compact will return early, not removing the NULL's. - // Inserting a NULL here ensures the compaction will take place. - // See http://www.openradar.me/15396578 and https://stackoverflow.com/a/40274426/1136669 - [_internalQueue addPointer:NULL]; - } - - [_internalQueue compact]; - if (_internalQueue.count == 0) { - isQueueDrained = YES; - } - } - - // itemsToProcess will be empty if _queueConsumer == nil so no need to check again. - const auto count = itemsToProcess.size(); - if (count > 0) { - const auto itemsEnd = itemsToProcess.cend(); - for (auto iterator = itemsToProcess.begin(); iterator < itemsEnd; iterator++) { - __unsafe_unretained id value = *iterator; - _queueConsumer(value, isQueueDrained && iterator == itemsEnd - 1); - } - } - - // If the queue is not fully drained yet force another run loop to process next batch of items - if (!isQueueDrained) { - CFRunLoopSourceSignal(_runLoopSource); - CFRunLoopWakeUp(_runLoop); - } - - ASSignpostEnd(ASSignpostRunLoopQueueBatch); -} - -- (void)enqueue:(id)object -{ - if (!object) { - return; - } - - MutexLocker l(_internalQueueLock); - - // Check if the object exists. - BOOL foundObject = NO; - - if (_ensureExclusiveMembership) { - for (id currentObject in _internalQueue) { - if (currentObject == object) { - foundObject = YES; - break; - } - } - } - - if (!foundObject) { - [_internalQueue addPointer:(__bridge void *)object]; - if (_internalQueue.count == 1) { - CFRunLoopSourceSignal(_runLoopSource); - CFRunLoopWakeUp(_runLoop); - } - } -} - -- (BOOL)isEmpty -{ - MutexLocker l(_internalQueueLock); - return _internalQueue.count == 0; -} - -ASSynthesizeLockingMethodsWithMutex(_internalQueueLock) - -@end - -#pragma mark - ASCATransactionQueue - -@interface ASCATransactionQueue () { - CFRunLoopSourceRef _runLoopSource; - CFRunLoopObserverRef _preTransactionObserver; - - // Current buffer for new entries, only accessed from within its mutex. - std::vector> _internalQueue; - - // No retain, no release, pointer hash, pointer equality. - // Enforce uniqueness in our queue. std::unordered_set does a heap allocation for each entry – not good. - CFMutableSetRef _internalQueueHashSet; - - // Temporary buffer, only accessed from the main thread in -process. - std::vector> _batchBuffer; - - AS::Mutex _internalQueueLock; - - // In order to not pollute the top-level activities, each queue has 1 root activity. - -#if ASRunLoopQueueLoggingEnabled - NSTimer *_runloopQueueLoggingTimer; -#endif -} - -@end - -@implementation ASCATransactionQueue - -// CoreAnimation commit order is 2000000, the goal of this is to process shortly beforehand -// but after most other scheduled work on the runloop has processed. -static int const kASASCATransactionQueueOrder = 1000000; - -ASCATransactionQueue *_ASSharedCATransactionQueue; -dispatch_once_t _ASSharedCATransactionQueueOnceToken; - -- (instancetype)init -{ - if (self = [super init]) { - _internalQueueHashSet = CFSetCreateMutable(NULL, 0, NULL); - - // This is going to be a very busy queue – every node in the preload range will enter this queue. - // Save some time on first render by reserving space up front. - static constexpr int kInternalQueueInitialCapacity = 64; - _internalQueue.reserve(kInternalQueueInitialCapacity); - _batchBuffer.reserve(kInternalQueueInitialCapacity); - - // Self is guaranteed to outlive the observer. Without the high cost of a weak pointer, - // __unsafe_unretained allows us to avoid flagging the memory cycle detector. - __unsafe_unretained __typeof__(self) weakSelf = self; - _preTransactionObserver = CFRunLoopObserverCreateWithHandler(NULL, kCFRunLoopBeforeWaiting, true, kASASCATransactionQueueOrder, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) { - while (!weakSelf->_internalQueue.empty()) { - [weakSelf processQueue]; - } - }); - - CFRunLoopAddObserver(CFRunLoopGetMain(), _preTransactionObserver, kCFRunLoopCommonModes); - - // It is not guaranteed that the runloop will turn if it has no scheduled work, and this causes processing of - // the queue to stop. Attaching a custom loop source to the run loop and signal it if new work needs to be done - CFRunLoopSourceContext sourceContext = {}; - sourceContext.perform = runLoopSourceCallback; -#if ASRunLoopQueueLoggingEnabled - sourceContext.info = (__bridge void *)self; -#endif - _runLoopSource = CFRunLoopSourceCreate(NULL, 0, &sourceContext); - CFRunLoopAddSource(CFRunLoopGetMain(), _runLoopSource, kCFRunLoopCommonModes); - -#if ASRunLoopQueueLoggingEnabled - _runloopQueueLoggingTimer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(checkRunLoop) userInfo:nil repeats:YES]; - [[NSRunLoop mainRunLoop] addTimer:_runloopQueueLoggingTimer forMode:NSRunLoopCommonModes]; -#endif - } - return self; -} - -- (void)dealloc -{ - ASDisplayNodeAssertMainThread(); - - CFRelease(_internalQueueHashSet); - CFRunLoopRemoveSource(CFRunLoopGetMain(), _runLoopSource, kCFRunLoopCommonModes); - CFRelease(_runLoopSource); - _runLoopSource = nil; - - if (CFRunLoopObserverIsValid(_preTransactionObserver)) { - CFRunLoopObserverInvalidate(_preTransactionObserver); - } - CFRelease(_preTransactionObserver); - _preTransactionObserver = nil; -} - -#if ASRunLoopQueueLoggingEnabled -- (void)checkRunLoop -{ - NSLog(@"<%@> - Jobs: %ld", self, _internalQueue.count); -} -#endif - -- (void)processQueue -{ - ASDisplayNodeAssertMainThread(); - - AS::UniqueLock l(_internalQueueLock); - NSInteger count = _internalQueue.size(); - // Early-exit if the queue is empty. - if (count == 0) { - return; - } - ASSignpostStart(ASSignpostRunLoopQueueBatch); - - // Swap buffers, clear our hash table. - _internalQueue.swap(_batchBuffer); - CFSetRemoveAllValues(_internalQueueHashSet); - - // Unlock early. We are done with internal queue, and batch buffer is main-thread-only so no lock. - l.unlock(); - - for (const id &value : _batchBuffer) { - [value prepareForCATransactionCommit]; - } - _batchBuffer.clear(); - ASSignpostEnd(ASSignpostRunLoopQueueBatch); -} - -- (void)enqueue:(id)object -{ - if (!object) { - return; - } - - if (!self.enabled) { - [object prepareForCATransactionCommit]; - return; - } - - MutexLocker l(_internalQueueLock); - if (CFSetContainsValue(_internalQueueHashSet, (__bridge void *)object)) { - return; - } - CFSetAddValue(_internalQueueHashSet, (__bridge void *)object); - _internalQueue.emplace_back(object); - if (_internalQueue.size() == 1) { - CFRunLoopSourceSignal(_runLoopSource); - CFRunLoopWakeUp(CFRunLoopGetMain()); - } -} - -- (BOOL)isEmpty -{ - MutexLocker l(_internalQueueLock); - return _internalQueue.empty(); -} - -- (BOOL)isEnabled -{ - return ASActivateExperimentalFeature(ASExperimentalInterfaceStateCoalescing); -} - -@end diff --git a/submodules/AsyncDisplayKit/Source/ASScrollDirection.mm b/submodules/AsyncDisplayKit/Source/ASScrollDirection.mm deleted file mode 100644 index 3dff6ba9b8..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASScrollDirection.mm +++ /dev/null @@ -1,64 +0,0 @@ -// -// ASScrollDirection.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -const ASScrollDirection ASScrollDirectionHorizontalDirections = ASScrollDirectionLeft | ASScrollDirectionRight; -const ASScrollDirection ASScrollDirectionVerticalDirections = ASScrollDirectionUp | ASScrollDirectionDown; - -BOOL ASScrollDirectionContainsVerticalDirection(ASScrollDirection scrollDirection) { - return (scrollDirection & ASScrollDirectionVerticalDirections) != 0; -} - -BOOL ASScrollDirectionContainsHorizontalDirection(ASScrollDirection scrollDirection) { - return (scrollDirection & ASScrollDirectionHorizontalDirections) != 0; -} - -BOOL ASScrollDirectionContainsRight(ASScrollDirection scrollDirection) { - return (scrollDirection & ASScrollDirectionRight) != 0; -} - -BOOL ASScrollDirectionContainsLeft(ASScrollDirection scrollDirection) { - return (scrollDirection & ASScrollDirectionLeft) != 0; -} - -BOOL ASScrollDirectionContainsUp(ASScrollDirection scrollDirection) { - return (scrollDirection & ASScrollDirectionUp) != 0; -} - -BOOL ASScrollDirectionContainsDown(ASScrollDirection scrollDirection) { - return (scrollDirection & ASScrollDirectionDown) != 0; -} - -ASScrollDirection ASScrollDirectionInvertHorizontally(ASScrollDirection scrollDirection) { - if (scrollDirection == ASScrollDirectionRight) { - return ASScrollDirectionLeft; - } else if (scrollDirection == ASScrollDirectionLeft) { - return ASScrollDirectionRight; - } - return scrollDirection; -} - -ASScrollDirection ASScrollDirectionInvertVertically(ASScrollDirection scrollDirection) { - if (scrollDirection == ASScrollDirectionUp) { - return ASScrollDirectionDown; - } else if (scrollDirection == ASScrollDirectionDown) { - return ASScrollDirectionUp; - } - return scrollDirection; -} - -ASScrollDirection ASScrollDirectionApplyTransform(ASScrollDirection scrollDirection, CGAffineTransform transform) { - if ((transform.a < 0) && ASScrollDirectionContainsHorizontalDirection(scrollDirection)) { - return ASScrollDirectionInvertHorizontally(scrollDirection); - } else if ((transform.d < 0) && ASScrollDirectionContainsVerticalDirection(scrollDirection)) { - return ASScrollDirectionInvertVertically(scrollDirection); - } - return scrollDirection; -} diff --git a/submodules/AsyncDisplayKit/Source/ASScrollNode.mm b/submodules/AsyncDisplayKit/Source/ASScrollNode.mm deleted file mode 100644 index 08fd82da80..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASScrollNode.mm +++ /dev/null @@ -1,178 +0,0 @@ -// -// ASScrollNode.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import -#import -#import -#import -#import -#import -#import - -@interface ASScrollView : UIScrollView -@end - -@implementation ASScrollView - -// This special +layerClass allows ASScrollNode to get -layout calls from -layoutSublayers. -+ (Class)layerClass -{ - return [_ASDisplayLayer class]; -} - -- (ASScrollNode *)scrollNode -{ - return (ASScrollNode *)ASViewToDisplayNode(self); -} - -- (BOOL)touchesShouldCancelInContentView:(UIView *)view { - if ([[self scrollNode] canCancelAllTouchesInViews]) { - return true; - } - return [super touchesShouldCancelInContentView:view]; -} - -#pragma mark - _ASDisplayView behavior substitutions -// Need these to drive interfaceState so we know when we are visible, if not nested in another range-managing element. -// Because our superclass is a true UIKit class, we cannot also subclass _ASDisplayView. -- (void)willMoveToWindow:(UIWindow *)newWindow -{ - ASDisplayNode *node = self.scrollNode; // Create strong reference to weak ivar. - BOOL visible = (newWindow != nil); - if (visible && !node.inHierarchy) { - [node __enterHierarchy]; - } -} - -- (void)didMoveToWindow -{ - ASDisplayNode *node = self.scrollNode; // Create strong reference to weak ivar. - BOOL visible = (self.window != nil); - if (!visible && node.inHierarchy) { - [node __exitHierarchy]; - } -} - -- (NSArray *)accessibilityElements -{ - return [self.asyncdisplaykit_node accessibilityElements]; -} - -@end - -@implementation ASScrollNode -{ - ASScrollDirection _scrollableDirections; - BOOL _automaticallyManagesContentSize; - CGSize _contentCalculatedSizeFromLayout; -} -@dynamic view; - -- (instancetype)init -{ - if (self = [super init]) { - [self setViewBlock:^UIView *{ return [[ASScrollView alloc] init]; }]; - } - return self; -} - -- (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize - restrictedToSize:(ASLayoutElementSize)size - relativeToParentSize:(CGSize)parentSize -{ - ASScopedLockSelfOrToRoot(); - - ASSizeRange contentConstrainedSize = constrainedSize; - if (ASScrollDirectionContainsVerticalDirection(_scrollableDirections)) { - contentConstrainedSize.max.height = CGFLOAT_MAX; - } - if (ASScrollDirectionContainsHorizontalDirection(_scrollableDirections)) { - contentConstrainedSize.max.width = CGFLOAT_MAX; - } - - ASLayout *layout = [super calculateLayoutThatFits:contentConstrainedSize - restrictedToSize:size - relativeToParentSize:parentSize]; - - if (_automaticallyManagesContentSize) { - // To understand this code, imagine we're containing a horizontal stack set within a vertical table node. - // Our parentSize is fixed ~375pt width, but 0 - INF height. Our stack measures 1000pt width, 50pt height. - // In this case, we want our scrollNode.bounds to be 375pt wide, and 50pt high. ContentSize 1000pt, 50pt. - // We can achieve this behavior by: - // 1. Always set contentSize to layout.size. - // 2. Set bounds to a size that is calculated by clamping parentSize against constrained size, - // unless one dimension is not defined, in which case adopt the contentSize for that dimension. - _contentCalculatedSizeFromLayout = layout.size; - CGSize selfSize = ASSizeRangeClamp(constrainedSize, parentSize); - if (ASPointsValidForLayout(selfSize.width) == NO) { - selfSize.width = _contentCalculatedSizeFromLayout.width; - } - if (ASPointsValidForLayout(selfSize.height) == NO) { - selfSize.height = _contentCalculatedSizeFromLayout.height; - } - // Don't provide a position, as that should be set by the parent. - layout = [ASLayout layoutWithLayoutElement:self - size:selfSize - sublayouts:layout.sublayouts]; - } - return layout; -} - -- (void)layout -{ - [super layout]; - - ASLockScopeSelf(); // Lock for using our two instance variables. - - if (_automaticallyManagesContentSize) { - CGSize contentSize = _contentCalculatedSizeFromLayout; - if (ASIsCGSizeValidForLayout(contentSize) == NO) { - NSLog(@"%@ calculated a size in its layout spec that can't be applied to .contentSize: %@. Applying parentSize (scrollNode's bounds) instead: %@.", self, NSStringFromCGSize(contentSize), NSStringFromCGSize(self.calculatedSize)); - contentSize = self.calculatedSize; - } - self.view.contentSize = contentSize; - } -} - -- (BOOL)automaticallyManagesContentSize -{ - ASLockScopeSelf(); - return _automaticallyManagesContentSize; -} - -- (void)setAutomaticallyManagesContentSize:(BOOL)automaticallyManagesContentSize -{ - ASLockScopeSelf(); - _automaticallyManagesContentSize = automaticallyManagesContentSize; - if (_automaticallyManagesContentSize == YES - && ASScrollDirectionContainsVerticalDirection(_scrollableDirections) == NO - && ASScrollDirectionContainsHorizontalDirection(_scrollableDirections) == NO) { - // Set the @default value, for more user-friendly behavior of the most - // common use cases of .automaticallyManagesContentSize. - _scrollableDirections = ASScrollDirectionVerticalDirections; - } -} - -- (ASScrollDirection)scrollableDirections -{ - ASLockScopeSelf(); - return _scrollableDirections; -} - -- (void)setScrollableDirections:(ASScrollDirection)scrollableDirections -{ - ASLockScopeSelf(); - if (_scrollableDirections != scrollableDirections) { - _scrollableDirections = scrollableDirections; - [self setNeedsLayout]; - } -} - -@end diff --git a/submodules/AsyncDisplayKit/Source/ASSignpost.h b/submodules/AsyncDisplayKit/Source/ASSignpost.h deleted file mode 100644 index a841794417..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASSignpost.h +++ /dev/null @@ -1,94 +0,0 @@ -// -// ASSignpost.h -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -/// The signposts we use. Signposts are grouped by color. The SystemTrace.tracetemplate file -/// should be kept up-to-date with these values. -typedef NS_ENUM(uint32_t, ASSignpostName) { - // Collection/Table (Blue) - ASSignpostDataControllerBatch = 300, // Alloc/layout nodes before collection update. - ASSignpostRangeControllerUpdate, // Ranges update pass. - ASSignpostCollectionUpdate, // Entire update process, from -endUpdates to [super perform…] - - // Rendering (Green) - ASSignpostLayerDisplay = 325, // Client display callout. - ASSignpostRunLoopQueueBatch, // One batch of ASRunLoopQueue. - - // Layout (Purple) - ASSignpostCalculateLayout = 350, // Start of calculateLayoutThatFits to end. Max 1 per thread. - - // Misc (Orange) - ASSignpostDeallocQueueDrain = 375, // One chunk of dealloc queue work. arg0 is count. - ASSignpostCATransactionLayout, // The CA transaction commit layout phase. - ASSignpostCATransactionCommit // The CA transaction commit post-layout phase. -}; - -typedef NS_ENUM(uintptr_t, ASSignpostColor) { - ASSignpostColorBlue, - ASSignpostColorGreen, - ASSignpostColorPurple, - ASSignpostColorOrange, - ASSignpostColorRed, - ASSignpostColorDefault -}; - -static inline ASSignpostColor ASSignpostGetColor(ASSignpostName name, ASSignpostColor colorPref) { - if (colorPref == ASSignpostColorDefault) { - return (ASSignpostColor)((name / 25) % 4); - } else { - return colorPref; - } -} - -#if defined(PROFILE) && __has_include() - #define AS_KDEBUG_ENABLE 1 -#else - #define AS_KDEBUG_ENABLE 0 -#endif - -#if AS_KDEBUG_ENABLE - -#import - -// These definitions are required to build the backward-compatible kdebug trace -// on the iOS 10 SDK. The kdebug_trace function crashes if run on iOS 9 and earlier. -// It's valuable to support trace signposts on iOS 9, because A5 devices don't support iOS 10. -#ifndef DBG_MACH_CHUD -#define DBG_MACH_CHUD 0x0A -#define DBG_FUNC_NONE 0 -#define DBG_FUNC_START 1 -#define DBG_FUNC_END 2 -#define DBG_APPS 33 -#define SYS_kdebug_trace 180 -#define KDBG_CODE(Class, SubClass, code) (((Class & 0xff) << 24) | ((SubClass & 0xff) << 16) | ((code & 0x3fff) << 2)) -#define APPSDBG_CODE(SubClass,code) KDBG_CODE(DBG_APPS, SubClass, code) -#endif - -// Currently we'll reserve arg3. -#define ASSignpost(name, identifier, arg2, color) \ -AS_AT_LEAST_IOS10 ? kdebug_signpost(name, (uintptr_t)identifier, (uintptr_t)arg2, 0, ASSignpostGetColor(name, color)) \ -: syscall(SYS_kdebug_trace, APPSDBG_CODE(DBG_MACH_CHUD, name) | DBG_FUNC_NONE, (uintptr_t)identifier, (uintptr_t)arg2, 0, ASSignpostGetColor(name, color)); - -#define ASSignpostStartCustom(name, identifier, arg2) \ -AS_AT_LEAST_IOS10 ? kdebug_signpost_start(name, (uintptr_t)identifier, (uintptr_t)arg2, 0, 0) \ -: syscall(SYS_kdebug_trace, APPSDBG_CODE(DBG_MACH_CHUD, name) | DBG_FUNC_START, (uintptr_t)identifier, (uintptr_t)arg2, 0, 0); -#define ASSignpostStart(name) ASSignpostStartCustom(name, self, 0) - -#define ASSignpostEndCustom(name, identifier, arg2, color) \ -AS_AT_LEAST_IOS10 ? kdebug_signpost_end(name, (uintptr_t)identifier, (uintptr_t)arg2, 0, ASSignpostGetColor(name, color)) \ -: syscall(SYS_kdebug_trace, APPSDBG_CODE(DBG_MACH_CHUD, name) | DBG_FUNC_END, (uintptr_t)identifier, (uintptr_t)arg2, 0, ASSignpostGetColor(name, color)); -#define ASSignpostEnd(name) ASSignpostEndCustom(name, self, 0, ASSignpostColorDefault) - -#else - -#define ASSignpost(name, identifier, arg2, color) -#define ASSignpostStartCustom(name, identifier, arg2) -#define ASSignpostStart(name) -#define ASSignpostEndCustom(name, identifier, arg2, color) -#define ASSignpostEnd(name) - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASTextKitComponents.mm b/submodules/AsyncDisplayKit/Source/ASTextKitComponents.mm deleted file mode 100644 index eeff17607d..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASTextKitComponents.mm +++ /dev/null @@ -1,194 +0,0 @@ -// -// ASTextKitComponents.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import -#import - -#import - -@interface ASTextKitComponentsTextView () { - // Prevent UITextView from updating contentOffset while deallocating: https://github.com/TextureGroup/Texture/issues/860 - BOOL _deallocating; -} -@property CGRect threadSafeBounds; -@end - -@implementation ASTextKitComponentsTextView - -- (instancetype)initWithFrame:(CGRect)frame textContainer:(NSTextContainer *)textContainer -{ - self = [super initWithFrame:frame textContainer:textContainer]; - if (self) { - _threadSafeBounds = self.bounds; - _deallocating = NO; - } - return self; -} - -- (void)dealloc -{ - _deallocating = YES; -} - -- (void)setFrame:(CGRect)frame -{ - ASDisplayNodeAssertMainThread(); - [super setFrame:frame]; - self.threadSafeBounds = self.bounds; -} - -- (void)setBounds:(CGRect)bounds -{ - ASDisplayNodeAssertMainThread(); - [super setBounds:bounds]; - self.threadSafeBounds = bounds; -} - -- (void)setContentOffset:(CGPoint)contentOffset -{ - if (_deallocating) { - return; - } - - [super setContentOffset:contentOffset]; -} - - -@end - -@interface ASTextKitComponents () - -// read-write redeclarations -@property (nonatomic) NSTextStorage *textStorage; -@property (nonatomic) NSTextContainer *textContainer; -@property (nonatomic) NSLayoutManager *layoutManager; - -@end - -@implementation ASTextKitComponents - -#pragma mark - Class - -+ (instancetype)componentsWithAttributedSeedString:(NSAttributedString *)attributedSeedString - textContainerSize:(CGSize)textContainerSize NS_RETURNS_RETAINED -{ - NSTextStorage *textStorage = attributedSeedString ? [[NSTextStorage alloc] initWithAttributedString:attributedSeedString] : [[NSTextStorage alloc] init]; - - return [self componentsWithTextStorage:textStorage - textContainerSize:textContainerSize - layoutManager:[[NSLayoutManager alloc] init]]; -} - -+ (instancetype)componentsWithTextStorage:(NSTextStorage *)textStorage - textContainerSize:(CGSize)textContainerSize - layoutManager:(NSLayoutManager *)layoutManager NS_RETURNS_RETAINED -{ - ASTextKitComponents *components = [[self alloc] init]; - - components.textStorage = textStorage; - - components.layoutManager = layoutManager; - [components.textStorage addLayoutManager:components.layoutManager]; - - components.textContainer = [[NSTextContainer alloc] initWithSize:textContainerSize]; - components.textContainer.lineFragmentPadding = 0.0; // We want the text laid out up to the very edges of the text-view. - [components.layoutManager addTextContainer:components.textContainer]; - - return components; -} - -+ (BOOL)needsMainThreadDeallocation -{ - return YES; -} - -#pragma mark - Lifecycle - -- (void)dealloc -{ - // Nil out all delegates to prevent crash - if (_textView) { - ASDisplayNodeAssertMainThread(); - _textView.delegate = nil; - } - _layoutManager.delegate = nil; -} - -#pragma mark - Sizing - -- (CGSize)sizeForConstrainedWidth:(CGFloat)constrainedWidth -{ - ASTextKitComponents *components = self; - - // If our text-view's width is already the constrained width, we can use our existing TextKit stack for this sizing calculation. - // Otherwise, we create a temporary stack to size for `constrainedWidth`. - if (CGRectGetWidth(components.textView.threadSafeBounds) != constrainedWidth) { - components = [ASTextKitComponents componentsWithAttributedSeedString:components.textStorage textContainerSize:CGSizeMake(constrainedWidth, CGFLOAT_MAX)]; - } - - // Force glyph generation and layout, which may not have happened yet (and isn't triggered by -usedRectForTextContainer:). - [components.layoutManager ensureLayoutForTextContainer:components.textContainer]; - CGSize textSize = [components.layoutManager usedRectForTextContainer:components.textContainer].size; - - return textSize; -} - -- (CGSize)sizeForConstrainedWidth:(CGFloat)constrainedWidth - forMaxNumberOfLines:(NSInteger)maxNumberOfLines -{ - if (maxNumberOfLines == 0) { - return [self sizeForConstrainedWidth:constrainedWidth]; - } - - ASTextKitComponents *components = self; - - // Always use temporary stack in case of threading issues - components = [ASTextKitComponents componentsWithAttributedSeedString:components.textStorage textContainerSize:CGSizeMake(constrainedWidth, CGFLOAT_MAX)]; - - // Force glyph generation and layout, which may not have happened yet (and isn't triggered by - usedRectForTextContainer:). - [components.layoutManager ensureLayoutForTextContainer:components.textContainer]; - - CGFloat width = [components.layoutManager usedRectForTextContainer:components.textContainer].size.width; - - // Calculate height based on line fragments - // Based on calculating number of lines from: http://asciiwwdc.com/2013/sessions/220 - NSRange glyphRange, lineRange = NSMakeRange(0, 0); - CGRect rect = CGRectZero; - CGFloat height = 0; - CGFloat lastOriginY = -1.0; - NSUInteger numberOfLines = 0; - - glyphRange = [components.layoutManager glyphRangeForTextContainer:components.textContainer]; - - while (lineRange.location < NSMaxRange(glyphRange)) { - rect = [components.layoutManager lineFragmentRectForGlyphAtIndex:lineRange.location - effectiveRange:&lineRange]; - - if (CGRectGetMinY(rect) > lastOriginY) { - ++numberOfLines; - if (numberOfLines == maxNumberOfLines) { - height = rect.origin.y + rect.size.height; - break; - } - } - - lastOriginY = CGRectGetMinY(rect); - lineRange.location = NSMaxRange(lineRange); - } - - CGFloat fragmentHeight = rect.origin.y + rect.size.height; - CGFloat finalHeight = std::ceil(std::fmax(height, fragmentHeight)); - - CGSize size = CGSizeMake(width, finalHeight); - - return size; -} - -@end diff --git a/submodules/AsyncDisplayKit/Source/ASTextKitContext.h b/submodules/AsyncDisplayKit/Source/ASTextKitContext.h deleted file mode 100644 index 82a40b7d8d..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASTextKitContext.h +++ /dev/null @@ -1,53 +0,0 @@ -// -// ASTextKitContext.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#import - -#if AS_ENABLE_TEXTNODE - -#import - -/** - A threadsafe container for the TextKit components that ASTextKit uses to lay out and truncate its text. - - This container is the sole owner and manager of the TextKit classes. This is an important model because of major - thread safety issues inside vanilla TextKit. It provides a central locking location for accessing TextKit methods. - */ -AS_SUBCLASSING_RESTRICTED -@interface ASTextKitContext : NSObject - -/** - Initializes a context and its associated TextKit components. - - Initialization of TextKit components is a globally locking operation so be careful of bottlenecks with this class. - */ -- (instancetype)initWithAttributedString:(NSAttributedString *)attributedString - lineBreakMode:(NSLineBreakMode)lineBreakMode - maximumNumberOfLines:(NSUInteger)maximumNumberOfLines - exclusionPaths:(NSArray *)exclusionPaths - constrainedSize:(CGSize)constrainedSize; - -/** - All operations on TextKit values MUST occur within this locked context. Simultaneous access (even non-mutative) to - TextKit components may cause crashes. - - The block provided MUST not call out to client code from within its scope or it is possible for this to cause deadlocks - in your application. Use with EXTREME care. - - Callers MUST NOT keep a ref to these internal objects and use them later. This WILL cause crashes in your application. - */ -- (void)performBlockWithLockedTextKitComponents:(AS_NOESCAPE void (^)(NSLayoutManager *layoutManager, - NSTextStorage *textStorage, - NSTextContainer *textContainer))block; - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASTextKitContext.mm b/submodules/AsyncDisplayKit/Source/ASTextKitContext.mm deleted file mode 100644 index 42f4a1a45c..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASTextKitContext.mm +++ /dev/null @@ -1,84 +0,0 @@ -// -// ASTextKitContext.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import "ASTextKitContext.h" - -#if AS_ENABLE_TEXTNODE - -#import "ASLayoutManager.h" -#import - -#include - -@implementation ASTextKitContext -{ - // All TextKit operations (even non-mutative ones) must be executed serially. - std::shared_ptr __instanceLock__; - - NSLayoutManager *_layoutManager; - NSTextStorage *_textStorage; - NSTextContainer *_textContainer; -} - -- (instancetype)initWithAttributedString:(NSAttributedString *)attributedString - lineBreakMode:(NSLineBreakMode)lineBreakMode - maximumNumberOfLines:(NSUInteger)maximumNumberOfLines - exclusionPaths:(NSArray *)exclusionPaths - constrainedSize:(CGSize)constrainedSize - -{ - if (self = [super init]) { - static dispatch_once_t onceToken; - static AS::Mutex *mutex; - dispatch_once(&onceToken, ^{ - mutex = new AS::Mutex(); - }); - - // Concurrently initialising TextKit components crashes (rdar://18448377) so we use a global lock. - AS::MutexLocker l(*mutex); - - __instanceLock__ = std::make_shared(); - - // Create the TextKit component stack with our default configuration. - - _textStorage = [[NSTextStorage alloc] init]; - _layoutManager = [[ASLayoutManager alloc] init]; - _layoutManager.usesFontLeading = NO; - [_textStorage addLayoutManager:_layoutManager]; - - // Instead of calling [NSTextStorage initWithAttributedString:], setting attributedString just after calling addlayoutManager can fix CJK language layout issues. - // See https://github.com/facebook/AsyncDisplayKit/issues/2894 - if (attributedString) { - [_textStorage setAttributedString:attributedString]; - } - - _textContainer = [[NSTextContainer alloc] initWithSize:constrainedSize]; - // We want the text laid out up to the very edges of the container. - _textContainer.lineFragmentPadding = 0; - _textContainer.lineBreakMode = lineBreakMode; - _textContainer.maximumNumberOfLines = maximumNumberOfLines; - _textContainer.exclusionPaths = exclusionPaths; - [_layoutManager addTextContainer:_textContainer]; - } - return self; -} - -- (void)performBlockWithLockedTextKitComponents:(NS_NOESCAPE void (^)(NSLayoutManager *, - NSTextStorage *, - NSTextContainer *))block -{ - AS::MutexLocker l(*__instanceLock__); - if (block) { - block(_layoutManager, _textStorage, _textContainer); - } -} - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASTextNodeCommon.h b/submodules/AsyncDisplayKit/Source/ASTextNodeCommon.h deleted file mode 100644 index 765f401c2a..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASTextNodeCommon.h +++ /dev/null @@ -1,34 +0,0 @@ -// -// ASTextNodeCommon.h -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#import - -#define AS_TEXT_ALERT_UNIMPLEMENTED_FEATURE() { \ - static dispatch_once_t onceToken; \ - dispatch_once(&onceToken, ^{ \ - NSLog(@"[Texture] Warning: Feature %@ is unimplemented in %@.", NSStringFromSelector(_cmd), NSStringFromClass(self.class)); \ - });\ -} - -/** - * Highlight styles. - */ -typedef NS_ENUM(NSUInteger, ASTextNodeHighlightStyle) { - /** - * Highlight style for text on a light background. - */ - ASTextNodeHighlightStyleLight, - - /** - * Highlight style for text on a dark background. - */ - ASTextNodeHighlightStyleDark -}; - diff --git a/submodules/AsyncDisplayKit/Source/ASTextNodeWordKerner.h b/submodules/AsyncDisplayKit/Source/ASTextNodeWordKerner.h deleted file mode 100644 index 795c4ea099..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASTextNodeWordKerner.h +++ /dev/null @@ -1,37 +0,0 @@ -// -// ASTextNodeWordKerner.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import - -#import - -NS_ASSUME_NONNULL_BEGIN - -/** - @abstract This class acts as the NSLayoutManagerDelegate for ASTextNode. - @discussion Its current job is word kerning, i.e. adjusting the width of spaces to match the set - wordKernedSpaceWidth. If word kerning is not needed, set the layoutManager's delegate to nil. - */ -AS_SUBCLASSING_RESTRICTED -@interface ASTextNodeWordKerner : NSObject - -/** - The following @optional NSLayoutManagerDelegate methods are implemented: - -- (NSUInteger)layoutManager:(NSLayoutManager *)layoutManager shouldGenerateGlyphs:(const CGGlyph *)glyphs properties:(const NSGlyphProperty *)props characterIndexes:(const NSUInteger *)charIndexes font:(UIFont *)aFont forGlyphRange:(NSRange)glyphRange NS_AVAILABLE_IOS(7_0); - -- (NSControlCharacterAction)layoutManager:(NSLayoutManager *)layoutManager shouldUseAction:(NSControlCharacterAction)action forControlCharacterAtIndex:(NSUInteger)charIndex NS_AVAILABLE_IOS(7_0); - -- (CGRect)layoutManager:(NSLayoutManager *)layoutManager boundingBoxForControlGlyphAtIndex:(NSUInteger)glyphIndex forTextContainer:(NSTextContainer *)textContainer proposedLineFragment:(CGRect)proposedRect glyphPosition:(CGPoint)glyphPosition characterIndex:(NSUInteger)charIndex NS_AVAILABLE_IOS(7_0); - */ - -@end - -NS_ASSUME_NONNULL_END diff --git a/submodules/AsyncDisplayKit/Source/ASTextNodeWordKerner.mm b/submodules/AsyncDisplayKit/Source/ASTextNodeWordKerner.mm deleted file mode 100644 index e1d0c73c0e..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASTextNodeWordKerner.mm +++ /dev/null @@ -1,130 +0,0 @@ -// -// ASTextNodeWordKerner.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import "ASTextNodeWordKerner.h" - -#import - -#import - -@implementation ASTextNodeWordKerner - -#pragma mark - NSLayoutManager Delegate -- (NSUInteger)layoutManager:(NSLayoutManager *)layoutManager shouldGenerateGlyphs:(const CGGlyph *)glyphs properties:(const NSGlyphProperty *)properties characterIndexes:(const NSUInteger *)characterIndexes font:(UIFont *)aFont forGlyphRange:(NSRange)glyphRange -{ - NSUInteger glyphCount = glyphRange.length; - NSGlyphProperty *newGlyphProperties = NULL; - - BOOL usesWordKerning = NO; - - // If our typing attributes specify word kerning, specify the spaces as whitespace control characters so we can customize their width. - // Are any of the characters spaces? - NSString *textStorageString = layoutManager.textStorage.string; - for (NSUInteger arrayIndex = 0; arrayIndex < glyphCount; arrayIndex++) { - NSUInteger characterIndex = characterIndexes[arrayIndex]; - if ([textStorageString characterAtIndex:characterIndex] != ' ') - continue; - - // If we've set the whitespace control character for this space already, we have nothing to do. - if (properties[arrayIndex] == NSGlyphPropertyControlCharacter) { - usesWordKerning = YES; - continue; - } - - // Create new glyph properties, if necessary. - if (!newGlyphProperties) { - newGlyphProperties = (NSGlyphProperty *)malloc(sizeof(NSGlyphProperty) * glyphCount); - memcpy(newGlyphProperties, properties, (sizeof(NSGlyphProperty) * glyphCount)); - } - - // It's a space. Make it a whitespace control character. - newGlyphProperties[arrayIndex] = NSGlyphPropertyControlCharacter; - } - - // If we don't have any custom glyph properties, return 0 to indicate to the layout manager that it should use the standard glyphs+properties. - if (!newGlyphProperties) { - if (usesWordKerning) { - // If the text does use word kerning we have to make sure we return the correct glyphCount, or the layout manager will just use the default properties and ignore our kerning. - [layoutManager setGlyphs:glyphs properties:properties characterIndexes:characterIndexes font:aFont forGlyphRange:glyphRange]; - return glyphCount; - } else { - return 0; - } - } - - // Otherwise, use our custom glyph properties. - [layoutManager setGlyphs:glyphs properties:newGlyphProperties characterIndexes:characterIndexes font:aFont forGlyphRange:glyphRange]; - free(newGlyphProperties); - - return glyphCount; -} - -- (NSControlCharacterAction)layoutManager:(NSLayoutManager *)layoutManager shouldUseAction:(NSControlCharacterAction)defaultAction forControlCharacterAtIndex:(NSUInteger)characterIndex -{ - // If it's a space character and we have custom word kerning, use the whitespace action control character. - if ([layoutManager.textStorage.string characterAtIndex:characterIndex] == ' ') - return NSControlCharacterActionWhitespace; - - return defaultAction; -} - -- (CGRect)layoutManager:(NSLayoutManager *)layoutManager boundingBoxForControlGlyphAtIndex:(NSUInteger)glyphIndex forTextContainer:(NSTextContainer *)textContainer proposedLineFragment:(CGRect)proposedRect glyphPosition:(CGPoint)glyphPosition characterIndex:(NSUInteger)characterIndex -{ - CGFloat wordKernedSpaceWidth = [self _wordKernedSpaceWidthForCharacterAtIndex:characterIndex atGlyphPosition:glyphPosition forTextContainer:textContainer layoutManager:layoutManager]; - return CGRectMake(glyphPosition.x, glyphPosition.y, wordKernedSpaceWidth, CGRectGetHeight(proposedRect)); -} - -- (CGFloat)_wordKernedSpaceWidthForCharacterAtIndex:(NSUInteger)characterIndex atGlyphPosition:(CGPoint)glyphPosition forTextContainer:(NSTextContainer *)textContainer layoutManager:(NSLayoutManager *)layoutManager -{ - // We use a map table for pointer equality and non-copying keys. - static NSMapTable *spaceSizes; - // NSMapTable is a defined thread unsafe class, so we need to synchronize - // access in a light manner. So we use dispatch_sync on this queue for all - // access to the map table. - static dispatch_queue_t mapQueue; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - spaceSizes = [[NSMapTable alloc] initWithKeyOptions:NSMapTableStrongMemory valueOptions:NSMapTableStrongMemory capacity:1]; - mapQueue = dispatch_queue_create("org.AsyncDisplayKit.wordKerningQueue", DISPATCH_QUEUE_SERIAL); - }); - CGFloat ordinarySpaceWidth; - UIFont *font = [layoutManager.textStorage attribute:NSFontAttributeName atIndex:characterIndex effectiveRange:NULL]; - CGFloat wordKerning = [[layoutManager.textStorage attribute:ASTextNodeWordKerningAttributeName atIndex:characterIndex effectiveRange:NULL] floatValue]; - __block NSNumber *ordinarySpaceSizeValue; - dispatch_sync(mapQueue, ^{ - ordinarySpaceSizeValue = [spaceSizes objectForKey:font]; - }); - if (ordinarySpaceSizeValue == nil) { - ordinarySpaceWidth = [@" " sizeWithAttributes:@{ NSFontAttributeName : font }].width; - dispatch_async(mapQueue, ^{ - [spaceSizes setObject:@(ordinarySpaceWidth) forKey:font]; - }); - } else { - ordinarySpaceWidth = [ordinarySpaceSizeValue floatValue]; - } - - CGFloat totalKernedWidth = (ordinarySpaceWidth + wordKerning); - - // TextKit normally handles whitespace by increasing the advance of the previous glyph, rather than displaying an - // actual glyph for the whitespace itself. However, in order to implement word kerning, we explicitly require a - // discrete glyph whose bounding box we can specify. The problem is that TextKit does not know this glyph is - // invisible. From TextKit's perspective, this whitespace glyph is a glyph that MUST be displayed. Thus when it - // comes to determining linebreaks, the width of this trailing whitespace glyph is considered. This causes - // our text to wrap sooner than it otherwise would, as room is allocated at the end of each line for a glyph that - // isn't actually visible. To implement our desired behavior, we check to see if the current whitespace glyph - // would break to the next line. If it breaks to the next line, then this constitutes trailing whitespace, and - // we specify enough room to fill up the remainder of the line, but nothing more. - if (glyphPosition.x + totalKernedWidth > textContainer.size.width) { - return (textContainer.size.width - glyphPosition.x); - } - - return totalKernedWidth; -} - -@end diff --git a/submodules/AsyncDisplayKit/Source/ASTraitCollection.mm b/submodules/AsyncDisplayKit/Source/ASTraitCollection.mm deleted file mode 100644 index e885239211..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASTraitCollection.mm +++ /dev/null @@ -1,256 +0,0 @@ -// -// ASTraitCollection.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import -#import -#import -#import - -#pragma mark - ASPrimitiveTraitCollection - -void ASTraitCollectionPropagateDown(id element, ASPrimitiveTraitCollection traitCollection) { - if (element) { - element.primitiveTraitCollection = traitCollection; - } - - for (id subelement in element.sublayoutElements) { - ASTraitCollectionPropagateDown(subelement, traitCollection); - } -} - -ASPrimitiveTraitCollection ASPrimitiveTraitCollectionMakeDefault() { - ASPrimitiveTraitCollection tc = {}; - tc.userInterfaceIdiom = UIUserInterfaceIdiomUnspecified; - tc.forceTouchCapability = UIForceTouchCapabilityUnknown; - tc.displayScale = 0.0; - tc.horizontalSizeClass = UIUserInterfaceSizeClassUnspecified; - tc.verticalSizeClass = UIUserInterfaceSizeClassUnspecified; - tc.containerSize = CGSizeZero; - if (AS_AVAILABLE_IOS(10)) { - tc.displayGamut = UIDisplayGamutUnspecified; - tc.preferredContentSizeCategory = UIContentSizeCategoryUnspecified; - tc.layoutDirection = UITraitEnvironmentLayoutDirectionUnspecified; - } -#if AS_BUILD_UIUSERINTERFACESTYLE - if (AS_AVAILABLE_IOS_TVOS(12, 10)) { - tc.userInterfaceStyle = UIUserInterfaceStyleUnspecified; - } -#endif - return tc; -} - -ASPrimitiveTraitCollection ASPrimitiveTraitCollectionFromUITraitCollection(UITraitCollection *traitCollection) { - ASPrimitiveTraitCollection environmentTraitCollection = ASPrimitiveTraitCollectionMakeDefault(); - environmentTraitCollection.horizontalSizeClass = traitCollection.horizontalSizeClass; - environmentTraitCollection.verticalSizeClass = traitCollection.verticalSizeClass; - environmentTraitCollection.displayScale = traitCollection.displayScale; - environmentTraitCollection.userInterfaceIdiom = traitCollection.userInterfaceIdiom; - environmentTraitCollection.forceTouchCapability = traitCollection.forceTouchCapability; - if (AS_AVAILABLE_IOS(10)) { - environmentTraitCollection.displayGamut = traitCollection.displayGamut; - environmentTraitCollection.layoutDirection = traitCollection.layoutDirection; - - ASDisplayNodeCAssertPermanent(traitCollection.preferredContentSizeCategory); - environmentTraitCollection.preferredContentSizeCategory = traitCollection.preferredContentSizeCategory; - } -#if AS_BUILD_UIUSERINTERFACESTYLE - if (AS_AVAILABLE_IOS_TVOS(12, 10)) { - environmentTraitCollection.userInterfaceStyle = traitCollection.userInterfaceStyle; - } -#endif - return environmentTraitCollection; -} - -BOOL ASPrimitiveTraitCollectionIsEqualToASPrimitiveTraitCollection(ASPrimitiveTraitCollection lhs, ASPrimitiveTraitCollection rhs) { - return !memcmp(&lhs, &rhs, sizeof(ASPrimitiveTraitCollection)); -} - -// Named so as not to conflict with a hidden Apple function, in case compiler decides not to inline -ASDISPLAYNODE_INLINE NSString *AS_NSStringFromUIUserInterfaceIdiom(UIUserInterfaceIdiom idiom) { - switch (idiom) { - case UIUserInterfaceIdiomTV: - return @"TV"; - case UIUserInterfaceIdiomPad: - return @"Pad"; - case UIUserInterfaceIdiomPhone: - return @"Phone"; - case UIUserInterfaceIdiomCarPlay: - return @"CarPlay"; - default: - return @"Unspecified"; - } -} - -// Named so as not to conflict with a hidden Apple function, in case compiler decides not to inline -ASDISPLAYNODE_INLINE NSString *AS_NSStringFromUIForceTouchCapability(UIForceTouchCapability capability) { - switch (capability) { - case UIForceTouchCapabilityAvailable: - return @"Available"; - case UIForceTouchCapabilityUnavailable: - return @"Unavailable"; - default: - return @"Unknown"; - } -} - -// Named so as not to conflict with a hidden Apple function, in case compiler decides not to inline -ASDISPLAYNODE_INLINE NSString *AS_NSStringFromUIUserInterfaceSizeClass(UIUserInterfaceSizeClass sizeClass) { - switch (sizeClass) { - case UIUserInterfaceSizeClassCompact: - return @"Compact"; - case UIUserInterfaceSizeClassRegular: - return @"Regular"; - default: - return @"Unspecified"; - } -} - -// Named so as not to conflict with a hidden Apple function, in case compiler decides not to inline -API_AVAILABLE(ios(10)) -ASDISPLAYNODE_INLINE NSString *AS_NSStringFromUIDisplayGamut(UIDisplayGamut displayGamut) { - switch (displayGamut) { - case UIDisplayGamutSRGB: - return @"sRGB"; - case UIDisplayGamutP3: - return @"P3"; - default: - return @"Unspecified"; - } -} - -// Named so as not to conflict with a hidden Apple function, in case compiler decides not to inline -API_AVAILABLE(ios(10)) -ASDISPLAYNODE_INLINE NSString *AS_NSStringFromUITraitEnvironmentLayoutDirection(UITraitEnvironmentLayoutDirection layoutDirection) { - switch (layoutDirection) { - case UITraitEnvironmentLayoutDirectionLeftToRight: - return @"LeftToRight"; - case UITraitEnvironmentLayoutDirectionRightToLeft: - return @"RightToLeft"; - default: - return @"Unspecified"; - } -} - -// Named so as not to conflict with a hidden Apple function, in case compiler decides not to inline -#if AS_BUILD_UIUSERINTERFACESTYLE -API_AVAILABLE(tvos(10.0), ios(12.0)) -ASDISPLAYNODE_INLINE NSString *AS_NSStringFromUIUserInterfaceStyle(UIUserInterfaceStyle userInterfaceStyle) { - switch (userInterfaceStyle) { - case UIUserInterfaceStyleLight: - return @"Light"; - case UIUserInterfaceStyleDark: - return @"Dark"; - default: - return @"Unspecified"; - } -} -#endif - -NSString *NSStringFromASPrimitiveTraitCollection(ASPrimitiveTraitCollection traits) { - NSMutableArray *props = [NSMutableArray array]; - [props addObject:@{ @"verticalSizeClass": AS_NSStringFromUIUserInterfaceSizeClass(traits.verticalSizeClass) }]; - [props addObject:@{ @"horizontalSizeClass": AS_NSStringFromUIUserInterfaceSizeClass(traits.horizontalSizeClass) }]; - [props addObject:@{ @"displayScale": [NSString stringWithFormat: @"%.0lf", (double)traits.displayScale] }]; - [props addObject:@{ @"userInterfaceIdiom": AS_NSStringFromUIUserInterfaceIdiom(traits.userInterfaceIdiom) }]; - [props addObject:@{ @"forceTouchCapability": AS_NSStringFromUIForceTouchCapability(traits.forceTouchCapability) }]; -#if AS_BUILD_UIUSERINTERFACESTYLE - if (AS_AVAILABLE_IOS_TVOS(12, 10)) { - [props addObject:@{ @"userInterfaceStyle": AS_NSStringFromUIUserInterfaceStyle(traits.userInterfaceStyle) }]; - } -#endif - if (AS_AVAILABLE_IOS(10)) { - [props addObject:@{ @"layoutDirection": AS_NSStringFromUITraitEnvironmentLayoutDirection(traits.layoutDirection) }]; - [props addObject:@{ @"preferredContentSizeCategory": traits.preferredContentSizeCategory }]; - [props addObject:@{ @"displayGamut": AS_NSStringFromUIDisplayGamut(traits.displayGamut) }]; - } - [props addObject:@{ @"containerSize": NSStringFromCGSize(traits.containerSize) }]; - return ASObjectDescriptionMakeWithoutObject(props); -} - -#pragma mark - ASTraitCollection - -@implementation ASTraitCollection { - ASPrimitiveTraitCollection _prim; -} - -+ (ASTraitCollection *)traitCollectionWithASPrimitiveTraitCollection:(ASPrimitiveTraitCollection)traits NS_RETURNS_RETAINED { - ASTraitCollection *tc = [[ASTraitCollection alloc] init]; - if (AS_AVAILABLE_IOS(10)) { - ASDisplayNodeCAssertPermanent(traits.preferredContentSizeCategory); - } - tc->_prim = traits; - return tc; -} - -- (ASPrimitiveTraitCollection)primitiveTraitCollection { - return _prim; -} -- (UIUserInterfaceSizeClass)horizontalSizeClass -{ - return _prim.horizontalSizeClass; -} --(UIUserInterfaceSizeClass)verticalSizeClass -{ - return _prim.verticalSizeClass; -} -- (CGFloat)displayScale -{ - return _prim.displayScale; -} -- (UIDisplayGamut)displayGamut -{ - return _prim.displayGamut; -} -- (UIForceTouchCapability)forceTouchCapability -{ - return _prim.forceTouchCapability; -} -- (UITraitEnvironmentLayoutDirection)layoutDirection -{ - return _prim.layoutDirection; -} -- (CGSize)containerSize -{ - return _prim.containerSize; -} -#if AS_BUILD_UIUSERINTERFACESTYLE -- (UIUserInterfaceStyle)userInterfaceStyle -{ - return _prim.userInterfaceStyle; -} -#endif -- (UIContentSizeCategory)preferredContentSizeCategory -{ - return _prim.preferredContentSizeCategory; -} -- (NSUInteger)hash { - return ASHashBytes(&_prim, sizeof(ASPrimitiveTraitCollection)); -} - -- (BOOL)isEqual:(id)object { - if (!object || ![object isKindOfClass:ASTraitCollection.class]) { - return NO; - } - return [self isEqualToTraitCollection:object]; -} - -- (BOOL)isEqualToTraitCollection:(ASTraitCollection *)traitCollection -{ - if (traitCollection == nil) { - return NO; - } - - if (self == traitCollection) { - return YES; - } - return ASPrimitiveTraitCollectionIsEqualToASPrimitiveTraitCollection(_prim, traitCollection->_prim); -} - -@end diff --git a/submodules/AsyncDisplayKit/Source/ASWeakMap.h b/submodules/AsyncDisplayKit/Source/ASWeakMap.h deleted file mode 100644 index 1f413ca7ae..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASWeakMap.h +++ /dev/null @@ -1,59 +0,0 @@ -// -// ASWeakMap.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import - -NS_ASSUME_NONNULL_BEGIN - - -/** - * This class is used in conjunction with ASWeakMap. Instances of this type are returned by an ASWeakMap, - * must retain this value for as long as they want the entry to exist in the map. - */ -AS_SUBCLASSING_RESTRICTED -@interface ASWeakMapEntry : NSObject - -@property (readonly) Value value; - -@end - - -/** - * This is not a full-featured map. It does not support features like `count` and FastEnumeration because there - * is not currently a need. - * - * This is a map that does not retain keys or values added to it. When both getting and setting, the caller is - * returned a ASWeakMapEntry and must retain it for as long as it wishes the key/value to remain in the map. - * We return a single Entry value to the caller to avoid two potential problems: - * - * 1) It's easier for callers to retain one value (the Entry) and not two (a key and a value). - * 2) Key values are tested for `isEqual` equality. If if a caller asks for a key "A" that is equal to a key "B" - * already in the map, then we need the caller to retain key "B" and not key "A". Returning an Entry simplifies - * the semantics. - * - * The underlying storage is a hash table and the Key type should implement `hash` and `isEqual:`. - */ -AS_SUBCLASSING_RESTRICTED -@interface ASWeakMap<__covariant Key, Value> : NSObject - -/** - * Read from the cache. The Value object is accessible from the returned ASWeakMapEntry. - */ -- (nullable ASWeakMapEntry *)entryForKey:(Key)key AS_WARN_UNUSED_RESULT; - -/** - * Put a value into the cache. If an entry with an equal key already exists, then the value is updated on the existing entry. - */ -- (ASWeakMapEntry *)setObject:(Value)value forKey:(Key)key AS_WARN_UNUSED_RESULT; - -@end - - -NS_ASSUME_NONNULL_END diff --git a/submodules/AsyncDisplayKit/Source/ASWeakMap.mm b/submodules/AsyncDisplayKit/Source/ASWeakMap.mm deleted file mode 100644 index 1267d8ff69..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASWeakMap.mm +++ /dev/null @@ -1,78 +0,0 @@ -// -// ASWeakMap.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import "ASWeakMap.h" - -@interface ASWeakMapEntry () -@property (nonatomic, readonly) id key; -@property id value; -@end - -@implementation ASWeakMapEntry - -- (instancetype)initWithKey:(id)key value:(id)value -{ - self = [super init]; - if (self) { - _key = key; - _value = value; - } - return self; -} - -@end - - -@interface ASWeakMap () -@property (nonatomic, readonly) NSMapTable *hashTable; -@end - -/** - * Implementation details: - * - * The retained size of our keys is potentially very large (for example, a UIImage is commonly part of a key). - * Unfortunately, NSMapTable does not make guarantees about how quickly it will dispose of entries where - * either the key or the value is weak and has been disposed. So, a NSMapTable with "strong key to weak value" is - * unsuitable for our purpose because the strong keys are retained longer than the value and for an indefininte period of time. - * More details here: http://cocoamine.net/blog/2013/12/13/nsmaptable-and-zeroing-weak-references/ - * - * Our NSMapTable is "weak key to weak value" where each key maps to an Entry. The Entry object is responsible - * for retaining both the key and value. Our convention is that the caller must retain the Entry object - * in order to keep the key and the value in the cache. - */ -@implementation ASWeakMap - -- (instancetype)init -{ - self = [super init]; - if (self) { - _hashTable = [NSMapTable weakToWeakObjectsMapTable]; - } - return self; -} - -- (ASWeakMapEntry *)entryForKey:(id)key -{ - return [self.hashTable objectForKey:key]; -} - -- (ASWeakMapEntry *)setObject:(id)value forKey:(id)key -{ - ASWeakMapEntry *entry = [self.hashTable objectForKey:key]; - if (entry != nil) { - // Update the value in the existing entry. - entry.value = value; - } else { - entry = [[ASWeakMapEntry alloc] initWithKey:key value:value]; - [self.hashTable setObject:entry forKey:key]; - } - return entry; -} - -@end diff --git a/submodules/AsyncDisplayKit/Source/ASWeakProxy.h b/submodules/AsyncDisplayKit/Source/ASWeakProxy.h deleted file mode 100644 index 7396474b1c..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASWeakProxy.h +++ /dev/null @@ -1,32 +0,0 @@ -// -// ASWeakProxy.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import - -AS_SUBCLASSING_RESTRICTED -@interface ASWeakProxy : NSProxy - -/** - * @return target The target which will be forwarded all messages sent to the weak proxy. - */ -@property (nonatomic, weak, readonly) id target; - -/** - * An object which forwards messages to a target which it weakly references - * - * @discussion This class is useful for breaking retain cycles. You can pass this in place - * of the target to something which creates a strong reference. All messages sent to the - * proxy will be passed onto the target. - * - * @return an instance of ASWeakProxy - */ -+ (instancetype)weakProxyWithTarget:(id)target NS_RETURNS_RETAINED; - -@end diff --git a/submodules/AsyncDisplayKit/Source/ASWeakProxy.mm b/submodules/AsyncDisplayKit/Source/ASWeakProxy.mm deleted file mode 100644 index 4a3b5c8a2a..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASWeakProxy.mm +++ /dev/null @@ -1,72 +0,0 @@ -// -// ASWeakProxy.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import "ASWeakProxy.h" -#import -#import - -@implementation ASWeakProxy - -- (instancetype)initWithTarget:(id)target -{ - if (self) { - _target = target; - } - return self; -} - -+ (instancetype)weakProxyWithTarget:(id)target NS_RETURNS_RETAINED -{ - return [[ASWeakProxy alloc] initWithTarget:target]; -} - -- (id)forwardingTargetForSelector:(SEL)aSelector -{ - return _target; -} - -- (BOOL)respondsToSelector:(SEL)aSelector -{ - return [_target respondsToSelector:aSelector]; -} - -- (BOOL)conformsToProtocol:(Protocol *)aProtocol -{ - return [_target conformsToProtocol:aProtocol]; -} - -/// Strangely, this method doesn't get forwarded by ObjC. -- (BOOL)isKindOfClass:(Class)aClass -{ - return [_target isKindOfClass:aClass]; -} - -- (NSString *)description -{ - return ASObjectDescriptionMake(self, @[@{ @"target": _target ?: (id)kCFNull }]); -} - -- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel -{ - ASDisplayNodeAssertNil(_target, @"ASWeakProxy got %@ when its target is still alive, which is unexpected.", NSStringFromSelector(_cmd)); - // Unfortunately, in order to get this object to work properly, the use of a method which creates an NSMethodSignature - // from a C string. -methodSignatureForSelector is called when a compiled definition for the selector cannot be found. - // This is the place where we have to create our own dud NSMethodSignature. This is necessary because if this method - // returns nil, a selector not found exception is raised. The string argument to -signatureWithObjCTypes: outlines - // the return type and arguments to the message. To return a dud NSMethodSignature, pretty much any signature will - // suffice. Since the -forwardInvocation call will do nothing if the target does not respond to the selector, - // the dud NSMethodSignature simply gets us around the exception. - return [NSMethodSignature signatureWithObjCTypes:"@^v^c"]; -} -- (void)forwardInvocation:(NSInvocation *)invocation -{ - ASDisplayNodeAssertNil(_target, @"ASWeakProxy got %@ when its target is still alive, which is unexpected.", NSStringFromSelector(_cmd)); -} - -@end diff --git a/submodules/AsyncDisplayKit/Source/ASWeakSet.mm b/submodules/AsyncDisplayKit/Source/ASWeakSet.mm deleted file mode 100644 index 6530271a5a..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASWeakSet.mm +++ /dev/null @@ -1,84 +0,0 @@ -// -// ASWeakSet.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -@interface ASWeakSet<__covariant ObjectType> () -@property (nonatomic, readonly) NSHashTable *hashTable; -@end - -@implementation ASWeakSet - -- (instancetype)init -{ - self = [super init]; - if (self) { - _hashTable = [NSHashTable hashTableWithOptions:NSHashTableWeakMemory | NSHashTableObjectPointerPersonality]; - } - return self; -} - -- (void)addObject:(id)object -{ - [_hashTable addObject:object]; -} - -- (void)removeObject:(id)object -{ - [_hashTable removeObject:object]; -} - -- (void)removeAllObjects -{ - [_hashTable removeAllObjects]; -} - -- (NSArray *)allObjects -{ - return _hashTable.allObjects; -} - -- (BOOL)containsObject:(id)object -{ - return [_hashTable containsObject:object]; -} - -- (BOOL)isEmpty -{ - return [_hashTable anyObject] == nil; -} - -/** - Note: The `count` property of NSHashTable is unreliable - in the case of weak-memory hash tables because entries - that have been deallocated are not removed immediately. - - In order to get the true count we have to fall back to using - fast enumeration. - */ -- (NSUInteger)count -{ - NSUInteger count = 0; - for (__unused id object in _hashTable) { - count += 1; - } - return count; -} - -- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(__unsafe_unretained id _Nonnull *)buffer count:(NSUInteger)len -{ - return [_hashTable countByEnumeratingWithState:state objects:buffer count:len]; -} - -- (NSString *)description -{ - return [[super description] stringByAppendingFormat:@" count: %tu, contents: %@", self.count, _hashTable]; -} - -@end diff --git a/submodules/AsyncDisplayKit/Source/NSArray+Diffing.mm b/submodules/AsyncDisplayKit/Source/NSArray+Diffing.mm deleted file mode 100644 index 83d32fea68..0000000000 --- a/submodules/AsyncDisplayKit/Source/NSArray+Diffing.mm +++ /dev/null @@ -1,177 +0,0 @@ -// -// NSArray+Diffing.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import -#import -#import - -@implementation NSArray (Diffing) - -typedef BOOL (^compareBlock)(id _Nonnull lhs, id _Nonnull rhs); - -- (void)asdk_diffWithArray:(NSArray *)array insertions:(NSIndexSet **)insertions deletions:(NSIndexSet **)deletions -{ - [self asdk_diffWithArray:array insertions:insertions deletions:deletions moves:nil compareBlock:[NSArray defaultCompareBlock]]; -} - -- (void)asdk_diffWithArray:(NSArray *)array insertions:(NSIndexSet **)insertions deletions:(NSIndexSet **)deletions - compareBlock:(compareBlock)comparison -{ - [self asdk_diffWithArray:array insertions:insertions deletions:deletions moves:nil compareBlock:comparison]; -} - -- (void)asdk_diffWithArray:(NSArray *)array insertions:(NSIndexSet **)insertions deletions:(NSIndexSet **)deletions - moves:(NSArray **)moves -{ - [self asdk_diffWithArray:array insertions:insertions deletions:deletions moves:moves - compareBlock:[NSArray defaultCompareBlock]]; -} - -- (void)asdk_diffWithArray:(NSArray *)array insertions:(NSIndexSet **)insertions deletions:(NSIndexSet **)deletions - moves:(NSArray **)moves compareBlock:(compareBlock)comparison -{ - struct NSObjectHash - { - std::size_t operator()(id k) const { return (std::size_t) [k hash]; }; - }; - struct NSObjectCompare - { - bool operator()(id lhs, id rhs) const { return (bool) [lhs isEqual:rhs]; }; - }; - std::unordered_multimap potentialMoves; - - NSAssert(comparison != nil, @"Comparison block is required"); - NSAssert(moves == nil || comparison == [NSArray defaultCompareBlock], @"move detection requires isEqual: and hash (no custom compare)"); - NSMutableArray *moveIndexPaths = nil; - NSMutableIndexSet *insertionIndexes = nil, *deletionIndexes = nil; - if (moves) { - moveIndexPaths = [NSMutableArray new]; - } - NSMutableIndexSet *commonIndexes = [self _asdk_commonIndexesWithArray:array compareBlock:comparison]; - - if (deletions || moves) { - deletionIndexes = [NSMutableIndexSet indexSet]; - NSUInteger i = 0; - for (id element in self) { - if (![commonIndexes containsIndex:i]) { - [deletionIndexes addIndex:i]; - } - if (moves) { - potentialMoves.insert(std::pair(element, i)); - } - ++i; - } - } - - if (insertions || moves) { - insertionIndexes = [NSMutableIndexSet indexSet]; - NSArray *commonObjects = [self objectsAtIndexes:commonIndexes]; - for (NSUInteger i = 0, j = 0; j < array.count; j++) { - auto moveFound = potentialMoves.find(array[j]); - NSUInteger movedFrom = NSNotFound; - if (moveFound != potentialMoves.end() && moveFound->second != j) { - movedFrom = moveFound->second; - potentialMoves.erase(moveFound); - [moveIndexPaths addObject:[NSIndexPath indexPathForItem:j inSection:movedFrom]]; - } - if (i < commonObjects.count && j < array.count && comparison(commonObjects[i], array[j])) { - i++; - } else { - if (movedFrom != NSNotFound) { - // moves will coalesce a delete / insert - the insert is just not done, and here we remove the delete: - [deletionIndexes removeIndex:movedFrom]; - // OR a move will have come from the LCS: - if ([commonIndexes containsIndex:movedFrom]) { - [commonIndexes removeIndex:movedFrom]; - commonObjects = [self objectsAtIndexes:commonIndexes]; - } - } else { - [insertionIndexes addIndex:j]; - } - } - } - } - - if (moves) {*moves = moveIndexPaths;} - if (deletions) {*deletions = deletionIndexes;} - if (insertions) {*insertions = insertionIndexes;} -} - -// https://github.com/raywenderlich/swift-algorithm-club/tree/master/Longest%20Common%20Subsequence is not exactly this code (obviously), but -// is a good commentary on the algorithm. -- (NSMutableIndexSet *)_asdk_commonIndexesWithArray:(NSArray *)array compareBlock:(BOOL (^)(id lhs, id rhs))comparison -{ - NSAssert(comparison != nil, @"Comparison block is required"); - - NSInteger selfCount = self.count; - NSInteger arrayCount = array.count; - - // Allocate the diff map in the heap so we don't blow the stack for large arrays. - NSInteger **lengths = NULL; - lengths = (NSInteger **)malloc(sizeof(NSInteger*) * (selfCount+1)); - if (lengths == NULL) { - ASDisplayNodeFailAssert(@"Failed to allocate memory for diffing"); - return nil; - } - // Fill in a LCS length matrix: - for (NSInteger i = 0; i <= selfCount; i++) { - lengths[i] = (NSInteger *)malloc(sizeof(NSInteger) * (arrayCount+1)); - if (lengths[i] == NULL) { - ASDisplayNodeFailAssert(@"Failed to allocate memory for diffing"); - return nil; - } - id selfObj = i > 0 ? self[i-1] : nil; - for (NSInteger j = 0; j <= arrayCount; j++) { - if (i == 0 || j == 0) { - lengths[i][j] = 0; - } else if (comparison(selfObj, array[j-1])) { - lengths[i][j] = 1 + lengths[i-1][j-1]; - } else { - lengths[i][j] = MAX(lengths[i-1][j], lengths[i][j-1]); - } - } - } - // Backtrack to fill in indices based on length matrix: - NSMutableIndexSet *common = [NSMutableIndexSet indexSet]; - NSInteger i = selfCount, j = arrayCount; - while(i > 0 && j > 0) { - if (comparison(self[i-1], array[j-1])) { - [common addIndex:(i-1)]; - i--; j--; - } else if (lengths[i-1][j] > lengths[i][j-1]) { - i--; - } else { - j--; - } - } - - for (NSInteger i = 0; i <= selfCount; i++) { - free(lengths[i]); - } - free(lengths); - return common; -} - -static compareBlock defaultCompare = nil; - -+ (compareBlock)defaultCompareBlock -{ - static dispatch_once_t onceToken; - - dispatch_once(&onceToken, ^{ - defaultCompare = ^BOOL(id lhs, id rhs) { - return [lhs isEqual:rhs]; - }; - }); - - return defaultCompare; -} - -@end diff --git a/submodules/AsyncDisplayKit/Source/NSIndexSet+ASHelpers.h b/submodules/AsyncDisplayKit/Source/NSIndexSet+ASHelpers.h deleted file mode 100644 index cab7c94310..0000000000 --- a/submodules/AsyncDisplayKit/Source/NSIndexSet+ASHelpers.h +++ /dev/null @@ -1,29 +0,0 @@ -// -// NSIndexSet+ASHelpers.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -@interface NSIndexSet (ASHelpers) - -- (NSIndexSet *)as_indexesByMapping:(NSUInteger (^)(NSUInteger idx))block; - -- (NSIndexSet *)as_intersectionWithIndexes:(NSIndexSet *)indexes; - -/// Returns all the item indexes from the given index paths that are in the given section. -+ (NSIndexSet *)as_indexSetFromIndexPaths:(NSArray *)indexPaths inSection:(NSUInteger)section; - -/// If you've got an old index, and you insert items using this index set, this returns the change to get to the new index. -- (NSUInteger)as_indexChangeByInsertingItemsBelowIndex:(NSUInteger)index; - -- (NSString *)as_smallDescription; - -/// Returns all the section indexes contained in the index paths array. -+ (NSIndexSet *)as_sectionsFromIndexPaths:(NSArray *)indexPaths; - -@end diff --git a/submodules/AsyncDisplayKit/Source/NSIndexSet+ASHelpers.mm b/submodules/AsyncDisplayKit/Source/NSIndexSet+ASHelpers.mm deleted file mode 100644 index 615e1749f0..0000000000 --- a/submodules/AsyncDisplayKit/Source/NSIndexSet+ASHelpers.mm +++ /dev/null @@ -1,91 +0,0 @@ -// -// NSIndexSet+ASHelpers.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -// UIKit indexPath helpers -#import - -#import "NSIndexSet+ASHelpers.h" - -@implementation NSIndexSet (ASHelpers) - -- (NSIndexSet *)as_indexesByMapping:(NSUInteger (^)(NSUInteger))block -{ - NSMutableIndexSet *result = [[NSMutableIndexSet alloc] init]; - [self enumerateRangesUsingBlock:^(NSRange range, BOOL * _Nonnull stop) { - for (NSUInteger i = range.location; i < NSMaxRange(range); i++) { - NSUInteger newIndex = block(i); - if (newIndex != NSNotFound) { - [result addIndex:newIndex]; - } - } - }]; - return result; -} - -- (NSIndexSet *)as_intersectionWithIndexes:(NSIndexSet *)indexes -{ - NSMutableIndexSet *result = [[NSMutableIndexSet alloc] init]; - [self enumerateRangesUsingBlock:^(NSRange range, BOOL * _Nonnull stop) { - [indexes enumerateRangesInRange:range options:kNilOptions usingBlock:^(NSRange range, BOOL * _Nonnull stop) { - [result addIndexesInRange:range]; - }]; - }]; - return result; -} - -+ (NSIndexSet *)as_indexSetFromIndexPaths:(NSArray *)indexPaths inSection:(NSUInteger)section -{ - NSMutableIndexSet *result = [[NSMutableIndexSet alloc] init]; - for (NSIndexPath *indexPath in indexPaths) { - if (indexPath.section == section) { - [result addIndex:indexPath.item]; - } - } - return result; -} - -- (NSUInteger)as_indexChangeByInsertingItemsBelowIndex:(NSUInteger)index -{ - __block NSUInteger newIndex = index; - [self enumerateRangesUsingBlock:^(NSRange range, BOOL * _Nonnull stop) { - for (NSUInteger i = range.location; i < NSMaxRange(range); i++) { - if (i <= newIndex) { - newIndex += 1; - } else { - *stop = YES; - } - } - }]; - return newIndex - index; -} - -- (NSString *)as_smallDescription -{ - NSMutableString *result = [NSMutableString stringWithString:@"{ "]; - [self enumerateRangesUsingBlock:^(NSRange range, BOOL * _Nonnull stop) { - if (range.length == 1) { - [result appendFormat:@"%tu ", range.location]; - } else { - [result appendFormat:@"%tu-%tu ", range.location, NSMaxRange(range) - 1]; - } - }]; - [result appendString:@"}"]; - return result; -} - -+ (NSIndexSet *)as_sectionsFromIndexPaths:(NSArray *)indexPaths -{ - NSMutableIndexSet *result = [[NSMutableIndexSet alloc] init]; - for (NSIndexPath *indexPath in indexPaths) { - [result addIndex:indexPath.section]; - } - return result; -} - -@end diff --git a/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASDisplayNode+FrameworkPrivate.h b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASDisplayNode+FrameworkPrivate.h index fab0646139..adb96c74c5 100644 --- a/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASDisplayNode+FrameworkPrivate.h +++ b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASDisplayNode+FrameworkPrivate.h @@ -317,12 +317,4 @@ __unused static NSString * _Nonnull NSStringFromASHierarchyStateChange(ASHierarc - (NSArray *)accessibilityElements; @end; -@interface UIView (ASDisplayNodeInternal) -@property (nullable, weak) ASDisplayNode *asyncdisplaykit_node; -@end - -@interface CALayer (ASDisplayNodeInternal) -@property (nullable, weak) ASDisplayNode *asyncdisplaykit_node; -@end - NS_ASSUME_NONNULL_END diff --git a/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASDisplayNode.h b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASDisplayNode.h index 0d2cae2951..6fa3718301 100644 --- a/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASDisplayNode.h +++ b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASDisplayNode.h @@ -985,4 +985,8 @@ typedef NS_ENUM(NSInteger, ASLayoutEngineType) { #define ASScopedLockSelfOrToRoot() ASLockScopeSelf() +@interface UIView (ASDisplayNodeInternal) +@property (nullable, weak) ASDisplayNode *asyncdisplaykit_node; +@end + NS_ASSUME_NONNULL_END diff --git a/submodules/AsyncDisplayKit/Source/UIResponder+AsyncDisplayKit.mm b/submodules/AsyncDisplayKit/Source/UIResponder+AsyncDisplayKit.mm deleted file mode 100644 index 9365fba5e0..0000000000 --- a/submodules/AsyncDisplayKit/Source/UIResponder+AsyncDisplayKit.mm +++ /dev/null @@ -1,32 +0,0 @@ -// -// UIResponder+AsyncDisplayKit.m -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#import -#import -#import "ASResponderChainEnumerator.h" - -@implementation UIResponder (AsyncDisplayKit) - -- (__kindof UIViewController *)asdk_associatedViewController -{ - ASDisplayNodeAssertMainThread(); - - for (UIResponder *responder in [self asdk_responderChainEnumerator]) { - UIViewController *vc = ASDynamicCast(responder, UIViewController); - if (vc) { - return vc; - } - } - return nil; -} - -@end - diff --git a/submodules/AsyncDisplayKit/Source/_ASAsyncTransaction.mm b/submodules/AsyncDisplayKit/Source/_ASAsyncTransaction.mm deleted file mode 100644 index c147f5f263..0000000000 --- a/submodules/AsyncDisplayKit/Source/_ASAsyncTransaction.mm +++ /dev/null @@ -1,465 +0,0 @@ -// -// _ASAsyncTransaction.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - - -#import -#import -#import -#import -#import -#import -#import - -#ifndef __STRICT_ANSI__ - #warning "Texture must be compiled with std=c++11 to prevent layout issues. gnu++ is not supported. This is hopefully temporary." -#endif - -AS_EXTERN NSRunLoopMode const UITrackingRunLoopMode; - -NSInteger const ASDefaultTransactionPriority = 0; - -@interface ASAsyncTransactionOperation : NSObject -- (instancetype)initWithOperationCompletionBlock:(asyncdisplaykit_async_transaction_operation_completion_block_t)operationCompletionBlock; -@property (nonatomic) asyncdisplaykit_async_transaction_operation_completion_block_t operationCompletionBlock; -@property id value; // set on bg queue by the operation block -@end - -@implementation ASAsyncTransactionOperation - -- (instancetype)initWithOperationCompletionBlock:(asyncdisplaykit_async_transaction_operation_completion_block_t)operationCompletionBlock -{ - if ((self = [super init])) { - _operationCompletionBlock = operationCompletionBlock; - } - return self; -} - -- (void)dealloc -{ - NSAssert(_operationCompletionBlock == nil, @"Should have been called and released before -dealloc"); -} - -- (void)callAndReleaseCompletionBlock:(BOOL)canceled; -{ - ASDisplayNodeAssertMainThread(); - if (_operationCompletionBlock) { - _operationCompletionBlock(self.value, canceled); - // Guarantee that _operationCompletionBlock is released on main thread - _operationCompletionBlock = nil; - } -} - -- (NSString *)description -{ - return [NSString stringWithFormat:@"", self, self.value]; -} - -@end - -// Lightweight operation queue for _ASAsyncTransaction that limits number of spawned threads -class ASAsyncTransactionQueue -{ -public: - - // Similar to dispatch_group_t - class Group - { - public: - // call when group is no longer needed; after last scheduled operation the group will delete itself - virtual void release() = 0; - - // schedule block on given queue - virtual void schedule(NSInteger priority, dispatch_queue_t queue, dispatch_block_t block) = 0; - - // dispatch block on given queue when all previously scheduled blocks finished executing - virtual void notify(dispatch_queue_t queue, dispatch_block_t block) = 0; - - // used when manually executing blocks - virtual void enter() = 0; - virtual void leave() = 0; - - // wait until all scheduled blocks finished executing - virtual void wait() = 0; - - protected: - virtual ~Group() { }; // call release() instead - }; - - // Create new group - Group *createGroup(); - - static ASAsyncTransactionQueue &instance(); - -private: - - struct GroupNotify - { - dispatch_block_t _block; - dispatch_queue_t _queue; - }; - - class GroupImpl : public Group - { - public: - GroupImpl(ASAsyncTransactionQueue &queue) - : _pendingOperations(0) - , _releaseCalled(false) - , _queue(queue) - { - } - - virtual void release(); - virtual void schedule(NSInteger priority, dispatch_queue_t queue, dispatch_block_t block); - virtual void notify(dispatch_queue_t queue, dispatch_block_t block); - virtual void enter(); - virtual void leave(); - virtual void wait(); - - int _pendingOperations; - std::list _notifyList; - std::condition_variable _condition; - BOOL _releaseCalled; - ASAsyncTransactionQueue &_queue; - }; - - struct Operation - { - dispatch_block_t _block; - GroupImpl *_group; - NSInteger _priority; - }; - - struct DispatchEntry // entry for each dispatch queue - { - typedef std::list OperationQueue; - typedef std::list OperationIteratorList; // each item points to operation queue - typedef std::map OperationPriorityMap; // sorted by priority - - OperationQueue _operationQueue; - OperationPriorityMap _operationPriorityMap; - int _threadCount; - - Operation popNextOperation(bool respectPriority); // assumes locked mutex - void pushOperation(Operation operation); // assumes locked mutex - }; - - std::map _entries; - std::mutex _mutex; -}; - -ASAsyncTransactionQueue::Group* ASAsyncTransactionQueue::createGroup() -{ - Group *res = new GroupImpl(*this); - return res; -} - -void ASAsyncTransactionQueue::GroupImpl::release() -{ - std::lock_guard l(_queue._mutex); - - if (_pendingOperations == 0) { - delete this; - } else { - _releaseCalled = YES; - } -} - -ASAsyncTransactionQueue::Operation ASAsyncTransactionQueue::DispatchEntry::popNextOperation(bool respectPriority) -{ - NSCAssert(!_operationQueue.empty() && !_operationPriorityMap.empty(), @"No scheduled operations available"); - - OperationQueue::iterator queueIterator; - OperationPriorityMap::iterator mapIterator; - - if (respectPriority) { - mapIterator = --_operationPriorityMap.end(); // highest priority "bucket" - queueIterator = *mapIterator->second.begin(); - } else { - queueIterator = _operationQueue.begin(); - mapIterator = _operationPriorityMap.find(queueIterator->_priority); - } - - // no matter what, first item in "bucket" must match item in queue - NSCAssert(mapIterator->second.front() == queueIterator, @"Queue inconsistency"); - - Operation res = *queueIterator; - _operationQueue.erase(queueIterator); - - mapIterator->second.pop_front(); - if (mapIterator->second.empty()) { - _operationPriorityMap.erase(mapIterator); - } - - return res; -} - -void ASAsyncTransactionQueue::DispatchEntry::pushOperation(ASAsyncTransactionQueue::Operation operation) -{ - _operationQueue.push_back(operation); - - OperationIteratorList &list = _operationPriorityMap[operation._priority]; - list.push_back(--_operationQueue.end()); -} - -void ASAsyncTransactionQueue::GroupImpl::schedule(NSInteger priority, dispatch_queue_t queue, dispatch_block_t block) -{ - ASAsyncTransactionQueue &q = _queue; - std::lock_guard l(q._mutex); - - DispatchEntry &entry = q._entries[queue]; - - Operation operation; - operation._block = block; - operation._group = this; - operation._priority = priority; - entry.pushOperation(operation); - - ++_pendingOperations; // enter group - -#if ASDISPLAYNODE_DELAY_DISPLAY - NSUInteger maxThreads = 1; -#else - NSUInteger maxThreads = [NSProcessInfo processInfo].activeProcessorCount * 2; - - // Bit questionable maybe - we can give main thread more CPU time during tracking. - if ([[NSRunLoop mainRunLoop].currentMode isEqualToString:UITrackingRunLoopMode]) - --maxThreads; -#endif - - if (entry._threadCount < maxThreads) { // we need to spawn another thread - - // first thread will take operations in queue order (regardless of priority), other threads will respect priority - bool respectPriority = entry._threadCount > 0; - ++entry._threadCount; - - dispatch_async(queue, ^{ - std::unique_lock lock(q._mutex); - - // go until there are no more pending operations - while (!entry._operationQueue.empty()) { - Operation operation = entry.popNextOperation(respectPriority); - lock.unlock(); - if (operation._block) { - operation._block(); - } - operation._group->leave(); - operation._block = nil; // the block must be freed while mutex is unlocked - lock.lock(); - } - --entry._threadCount; - - if (entry._threadCount == 0) { - NSCAssert(entry._operationQueue.empty() || entry._operationPriorityMap.empty(), @"No working threads but operations are still scheduled"); // this shouldn't happen - q._entries.erase(queue); - } - }); - } -} - -void ASAsyncTransactionQueue::GroupImpl::notify(dispatch_queue_t queue, dispatch_block_t block) -{ - std::lock_guard l(_queue._mutex); - - if (_pendingOperations == 0) { - dispatch_async(queue, block); - } else { - GroupNotify notify; - notify._block = block; - notify._queue = queue; - _notifyList.push_back(notify); - } -} - -void ASAsyncTransactionQueue::GroupImpl::enter() -{ - std::lock_guard l(_queue._mutex); - ++_pendingOperations; -} - -void ASAsyncTransactionQueue::GroupImpl::leave() -{ - std::lock_guard l(_queue._mutex); - --_pendingOperations; - - if (_pendingOperations == 0) { - std::list notifyList; - _notifyList.swap(notifyList); - - for (GroupNotify & notify : notifyList) { - dispatch_async(notify._queue, notify._block); - } - - _condition.notify_one(); - - // there was attempt to release the group before, but we still - // had operations scheduled so now is good time - if (_releaseCalled) { - delete this; - } - } -} - -void ASAsyncTransactionQueue::GroupImpl::wait() -{ - std::unique_lock lock(_queue._mutex); - while (_pendingOperations > 0) { - _condition.wait(lock); - } -} - -ASAsyncTransactionQueue & ASAsyncTransactionQueue::instance() -{ - static ASAsyncTransactionQueue *instance = new ASAsyncTransactionQueue(); - return *instance; -} - -@interface _ASAsyncTransaction () -@property ASAsyncTransactionState state; -@end - - -@implementation _ASAsyncTransaction -{ - ASAsyncTransactionQueue::Group *_group; - NSMutableArray *_operations; -} - -#pragma mark - Lifecycle - -- (instancetype)initWithCompletionBlock:(void(^)(_ASAsyncTransaction *, BOOL))completionBlock -{ - if ((self = [self init])) { - _completionBlock = completionBlock; - self.state = ASAsyncTransactionStateOpen; - } - return self; -} - -- (void)dealloc -{ - // Uncommitted transactions break our guarantees about releasing completion blocks on callbackQueue. - NSAssert(self.state != ASAsyncTransactionStateOpen, @"Uncommitted ASAsyncTransactions are not allowed"); - if (_group) { - _group->release(); - } -} - -#pragma mark - Transaction Management - -- (void)addOperationWithBlock:(asyncdisplaykit_async_transaction_operation_block_t)block - priority:(NSInteger)priority - queue:(dispatch_queue_t)queue - completion:(asyncdisplaykit_async_transaction_operation_completion_block_t)completion -{ - ASDisplayNodeAssertMainThread(); - NSAssert(self.state == ASAsyncTransactionStateOpen, @"You can only add operations to open transactions"); - - [self _ensureTransactionData]; - - ASAsyncTransactionOperation *operation = [[ASAsyncTransactionOperation alloc] initWithOperationCompletionBlock:completion]; - [_operations addObject:operation]; - _group->schedule(priority, queue, ^{ - @autoreleasepool { - if (self.state != ASAsyncTransactionStateCanceled) { - operation.value = block(); - } - } - }); -} - -- (void)cancel -{ - ASDisplayNodeAssertMainThread(); - NSAssert(self.state != ASAsyncTransactionStateOpen, @"You can only cancel a committed or already-canceled transaction"); - self.state = ASAsyncTransactionStateCanceled; -} - -- (void)commit -{ - ASDisplayNodeAssertMainThread(); - NSAssert(self.state == ASAsyncTransactionStateOpen, @"You cannot double-commit a transaction"); - self.state = ASAsyncTransactionStateCommitted; - - if ([_operations count] == 0) { - // Fast path: if a transaction was opened, but no operations were added, execute completion block synchronously. - if (_completionBlock) { - _completionBlock(self, NO); - } - } else { - NSAssert(_group != NULL, @"If there are operations, dispatch group should have been created"); - - _group->notify(dispatch_get_main_queue(), ^{ - [self completeTransaction]; - }); - } -} - -- (void)completeTransaction -{ - ASDisplayNodeAssertMainThread(); - ASAsyncTransactionState state = self.state; - if (state != ASAsyncTransactionStateComplete) { - BOOL isCanceled = (state == ASAsyncTransactionStateCanceled); - for (ASAsyncTransactionOperation *operation in _operations) { - [operation callAndReleaseCompletionBlock:isCanceled]; - } - - // Always set state to Complete, even if we were cancelled, to block any extraneous - // calls to this method that may have been scheduled for the next runloop - // (e.g. if we needed to force one in this runloop with -waitUntilComplete, but another was already scheduled) - self.state = ASAsyncTransactionStateComplete; - - if (_completionBlock) { - _completionBlock(self, isCanceled); - } - } -} - -- (void)waitUntilComplete -{ - ASDisplayNodeAssertMainThread(); - if (self.state != ASAsyncTransactionStateComplete) { - if (_group) { - _group->wait(); - - // At this point, the asynchronous operation may have completed, but the runloop - // observer has not committed the batch of transactions we belong to. It's important to - // commit ourselves via the group to avoid double-committing the transaction. - // This is only necessary when forcing display work to complete before allowing the runloop - // to continue, e.g. in the implementation of -[ASDisplayNode recursivelyEnsureDisplay]. - if (self.state == ASAsyncTransactionStateOpen) { - [_ASAsyncTransactionGroup.mainTransactionGroup commit]; - NSAssert(self.state != ASAsyncTransactionStateOpen, @"Transaction should not be open after committing group"); - } - // If we needed to commit the group above, -completeTransaction may have already been run. - // It is designed to accommodate this by checking _state to ensure it is not complete. - [self completeTransaction]; - } - } -} - -#pragma mark - Helper Methods - -- (void)_ensureTransactionData -{ - // Lazily initialize _group and _operations to avoid overhead in the case where no operations are added to the transaction - if (_group == NULL) { - _group = ASAsyncTransactionQueue::instance().createGroup(); - } - if (_operations == nil) { - _operations = [[NSMutableArray alloc] init]; - } -} - -- (NSString *)description -{ - return [NSString stringWithFormat:@"<_ASAsyncTransaction: %p - _state = %lu, _group = %p, _operations = %@>", self, (unsigned long)self.state, _group, _operations]; -} - -@end diff --git a/submodules/AsyncDisplayKit/Source/_ASAsyncTransactionContainer+Private.h b/submodules/AsyncDisplayKit/Source/_ASAsyncTransactionContainer+Private.h deleted file mode 100644 index 003184c586..0000000000 --- a/submodules/AsyncDisplayKit/Source/_ASAsyncTransactionContainer+Private.h +++ /dev/null @@ -1,24 +0,0 @@ -// -// _ASAsyncTransactionContainer+Private.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -@class _ASAsyncTransaction; - -@interface CALayer (ASAsyncTransactionContainerTransactions) -@property (nonatomic, nullable, setter=asyncdisplaykit_setAsyncLayerTransactions:) NSHashTable<_ASAsyncTransaction *> *asyncdisplaykit_asyncLayerTransactions; - -- (void)asyncdisplaykit_asyncTransactionContainerWillBeginTransaction:(_ASAsyncTransaction *)transaction; -- (void)asyncdisplaykit_asyncTransactionContainerDidCompleteTransaction:(_ASAsyncTransaction *)transaction; -@end - -NS_ASSUME_NONNULL_END diff --git a/submodules/AsyncDisplayKit/Source/_ASAsyncTransactionContainer.mm b/submodules/AsyncDisplayKit/Source/_ASAsyncTransactionContainer.mm deleted file mode 100644 index 5bbfd95f15..0000000000 --- a/submodules/AsyncDisplayKit/Source/_ASAsyncTransactionContainer.mm +++ /dev/null @@ -1,121 +0,0 @@ -// -// _ASAsyncTransactionContainer.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import "_ASAsyncTransactionContainer+Private.h" - -#import -#import - -@implementation CALayer (ASAsyncTransactionContainerTransactions) -@dynamic asyncdisplaykit_asyncLayerTransactions; - -// No-ops in the base class. Mostly exposed for testing. -- (void)asyncdisplaykit_asyncTransactionContainerWillBeginTransaction:(_ASAsyncTransaction *)transaction {} -- (void)asyncdisplaykit_asyncTransactionContainerDidCompleteTransaction:(_ASAsyncTransaction *)transaction {} -@end - -@implementation CALayer (ASAsyncTransactionContainer) -@dynamic asyncdisplaykit_currentAsyncTransaction; -@dynamic asyncdisplaykit_asyncTransactionContainer; - -- (ASAsyncTransactionContainerState)asyncdisplaykit_asyncTransactionContainerState -{ - return ([self.asyncdisplaykit_asyncLayerTransactions count] == 0) ? ASAsyncTransactionContainerStateNoTransactions : ASAsyncTransactionContainerStatePendingTransactions; -} - -- (void)asyncdisplaykit_cancelAsyncTransactions -{ - // If there was an open transaction, commit and clear the current transaction. Otherwise: - // (1) The run loop observer will try to commit a canceled transaction which is not allowed - // (2) We leave the canceled transaction attached to the layer, dooming future operations - _ASAsyncTransaction *currentTransaction = self.asyncdisplaykit_currentAsyncTransaction; - [currentTransaction commit]; - self.asyncdisplaykit_currentAsyncTransaction = nil; - - for (_ASAsyncTransaction *transaction in [self.asyncdisplaykit_asyncLayerTransactions copy]) { - [transaction cancel]; - } -} - -- (_ASAsyncTransaction *)asyncdisplaykit_asyncTransaction -{ - _ASAsyncTransaction *transaction = self.asyncdisplaykit_currentAsyncTransaction; - if (transaction == nil) { - NSHashTable *transactions = self.asyncdisplaykit_asyncLayerTransactions; - if (transactions == nil) { - transactions = [NSHashTable hashTableWithOptions:NSHashTableObjectPointerPersonality]; - self.asyncdisplaykit_asyncLayerTransactions = transactions; - } - __weak CALayer *weakSelf = self; - transaction = [[_ASAsyncTransaction alloc] initWithCompletionBlock:^(_ASAsyncTransaction *completedTransaction, BOOL cancelled) { - __strong CALayer *self = weakSelf; - if (self == nil) { - return; - } - [transactions removeObject:completedTransaction]; - [self asyncdisplaykit_asyncTransactionContainerDidCompleteTransaction:completedTransaction]; - }]; - [transactions addObject:transaction]; - self.asyncdisplaykit_currentAsyncTransaction = transaction; - [self asyncdisplaykit_asyncTransactionContainerWillBeginTransaction:transaction]; - } - [_ASAsyncTransactionGroup.mainTransactionGroup addTransactionContainer:self]; - return transaction; -} - -- (CALayer *)asyncdisplaykit_parentTransactionContainer -{ - CALayer *containerLayer = self; - while (containerLayer && !containerLayer.asyncdisplaykit_isAsyncTransactionContainer) { - containerLayer = containerLayer.superlayer; - } - return containerLayer; -} - -@end - -@implementation UIView (ASAsyncTransactionContainer) - -- (BOOL)asyncdisplaykit_isAsyncTransactionContainer -{ - return self.layer.asyncdisplaykit_isAsyncTransactionContainer; -} - -- (void)asyncdisplaykit_setAsyncTransactionContainer:(BOOL)asyncTransactionContainer -{ - self.layer.asyncdisplaykit_asyncTransactionContainer = asyncTransactionContainer; -} - -- (ASAsyncTransactionContainerState)asyncdisplaykit_asyncTransactionContainerState -{ - return self.layer.asyncdisplaykit_asyncTransactionContainerState; -} - -- (void)asyncdisplaykit_cancelAsyncTransactions -{ - [self.layer asyncdisplaykit_cancelAsyncTransactions]; -} - -- (void)asyncdisplaykit_asyncTransactionContainerStateDidChange -{ - // No-op in the base class. -} - -- (void)asyncdisplaykit_setCurrentAsyncTransaction:(_ASAsyncTransaction *)transaction -{ - self.layer.asyncdisplaykit_currentAsyncTransaction = transaction; -} - -- (_ASAsyncTransaction *)asyncdisplaykit_currentAsyncTransaction -{ - return self.layer.asyncdisplaykit_currentAsyncTransaction; -} - -@end diff --git a/submodules/AsyncDisplayKit/Source/_ASAsyncTransactionGroup.mm b/submodules/AsyncDisplayKit/Source/_ASAsyncTransactionGroup.mm deleted file mode 100644 index ca170229b8..0000000000 --- a/submodules/AsyncDisplayKit/Source/_ASAsyncTransactionGroup.mm +++ /dev/null @@ -1,88 +0,0 @@ -// -// _ASAsyncTransactionGroup.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#import -#import -#import -#import "_ASAsyncTransactionContainer+Private.h" - -@implementation _ASAsyncTransactionGroup { - NSHashTable> *_containers; -} - -+ (_ASAsyncTransactionGroup *)mainTransactionGroup -{ - ASDisplayNodeAssertMainThread(); - static _ASAsyncTransactionGroup *mainTransactionGroup; - - if (mainTransactionGroup == nil) { - mainTransactionGroup = [[_ASAsyncTransactionGroup alloc] _init]; - [mainTransactionGroup registerAsMainRunloopObserver]; - } - return mainTransactionGroup; -} - -- (void)registerAsMainRunloopObserver -{ - ASDisplayNodeAssertMainThread(); - static CFRunLoopObserverRef observer; - ASDisplayNodeAssert(observer == NULL, @"A _ASAsyncTransactionGroup should not be registered on the main runloop twice"); - // defer the commit of the transaction so we can add more during the current runloop iteration - CFRunLoopRef runLoop = CFRunLoopGetCurrent(); - CFOptionFlags activities = (kCFRunLoopBeforeWaiting | // before the run loop starts sleeping - kCFRunLoopExit); // before exiting a runloop run - - observer = CFRunLoopObserverCreateWithHandler(NULL, // allocator - activities, // activities - YES, // repeats - INT_MAX, // order after CA transaction commits - ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) { - ASDisplayNodeCAssertMainThread(); - [self commit]; - }); - CFRunLoopAddObserver(runLoop, observer, kCFRunLoopCommonModes); - CFRelease(observer); -} - -- (instancetype)_init -{ - if ((self = [super init])) { - _containers = [NSHashTable hashTableWithOptions:NSHashTableObjectPointerPersonality]; - } - return self; -} - -- (void)addTransactionContainer:(id)container -{ - ASDisplayNodeAssertMainThread(); - ASDisplayNodeAssert(container != nil, @"No container"); - [_containers addObject:container]; -} - -- (void)commit -{ - ASDisplayNodeAssertMainThread(); - - if ([_containers count]) { - NSHashTable *containersToCommit = _containers; - _containers = [NSHashTable hashTableWithOptions:NSHashTableObjectPointerPersonality]; - - for (id container in containersToCommit) { - // Note that the act of committing a transaction may open a new transaction, - // so we must nil out the transaction we're committing first. - _ASAsyncTransaction *transaction = container.asyncdisplaykit_currentAsyncTransaction; - container.asyncdisplaykit_currentAsyncTransaction = nil; - [transaction commit]; - } - } -} - -@end diff --git a/submodules/AsyncDisplayKit/Source/_ASCoreAnimationExtras.mm b/submodules/AsyncDisplayKit/Source/_ASCoreAnimationExtras.mm deleted file mode 100644 index b55bd6442f..0000000000 --- a/submodules/AsyncDisplayKit/Source/_ASCoreAnimationExtras.mm +++ /dev/null @@ -1,187 +0,0 @@ -// -// _ASCoreAnimationExtras.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import -#import - -void ASDisplayNodeSetupLayerContentsWithResizableImage(CALayer *layer, UIImage *image) -{ - ASDisplayNodeSetResizableContents(layer, image); -} - -void ASDisplayNodeSetResizableContents(id obj, UIImage *image) -{ - // FIXME (https://github.com/TextureGroup/Texture/issues/1046): This method does not currently handle UIImageResizingModeTile, which is the default. - // See also https://developer.apple.com/documentation/uikit/uiimage/1624157-resizingmode?language=objc - // I'm not sure of a way to use CALayer directly to perform such tiling on the GPU, though the stretch is handled by the GPU, - // and CALayer.h documents the fact that contentsCenter is used to stretch the pixels. - - if (image) { - ASDisplayNodeCAssert(image.resizingMode == UIImageResizingModeStretch || UIEdgeInsetsEqualToEdgeInsets(image.capInsets, UIEdgeInsetsZero), - @"Image insets must be all-zero or resizingMode has to be UIImageResizingModeStretch. XCode assets default value is UIImageResizingModeTile which is not supported by Texture because of GPU-accelerated CALayer features."); - - // Image may not actually be stretchable in one or both dimensions; this is handled - obj.contents = (id)[image CGImage]; - obj.contentsScale = [image scale]; - obj.rasterizationScale = [image scale]; - CGSize imageSize = [image size]; - - UIEdgeInsets insets = [image capInsets]; - - // These are lifted from what UIImageView does by experimentation. Without these exact values, the stretching is slightly off. - const CGFloat halfPixelFudge = 0.49f; - const CGFloat otherPixelFudge = 0.02f; - // Convert to unit coordinates for the contentsCenter property. - CGRect contentsCenter = CGRectMake(0.0f, 0.0f, 1.0f, 1.0f); - if (insets.left > 0 || insets.right > 0) { - contentsCenter.origin.x = ((insets.left + halfPixelFudge) / imageSize.width); - contentsCenter.size.width = (imageSize.width - (insets.left + insets.right + 1.f) + otherPixelFudge) / imageSize.width; - } - if (insets.top > 0 || insets.bottom > 0) { - contentsCenter.origin.y = ((insets.top + halfPixelFudge) / imageSize.height); - contentsCenter.size.height = (imageSize.height - (insets.top + insets.bottom + 1.f) + otherPixelFudge) / imageSize.height; - } - obj.contentsGravity = kCAGravityResize; - obj.contentsCenter = contentsCenter; - - } else { - obj.contents = nil; - } -} - - -struct _UIContentModeStringLUTEntry { - UIViewContentMode contentMode; - NSString *const string; -}; - -static const _UIContentModeStringLUTEntry *UIContentModeCAGravityLUT(size_t *count) -{ - // Initialize this in a function (instead of at file level) to avoid - // startup initialization time. - static const _UIContentModeStringLUTEntry sUIContentModeCAGravityLUT[] = { - {UIViewContentModeScaleToFill, kCAGravityResize}, - {UIViewContentModeScaleAspectFit, kCAGravityResizeAspect}, - {UIViewContentModeScaleAspectFill, kCAGravityResizeAspectFill}, - {UIViewContentModeCenter, kCAGravityCenter}, - {UIViewContentModeTop, kCAGravityBottom}, - {UIViewContentModeBottom, kCAGravityTop}, - {UIViewContentModeLeft, kCAGravityLeft}, - {UIViewContentModeRight, kCAGravityRight}, - {UIViewContentModeTopLeft, kCAGravityBottomLeft}, - {UIViewContentModeTopRight, kCAGravityBottomRight}, - {UIViewContentModeBottomLeft, kCAGravityTopLeft}, - {UIViewContentModeBottomRight, kCAGravityTopRight}, - }; - *count = sizeof(sUIContentModeCAGravityLUT) / sizeof(sUIContentModeCAGravityLUT[0]); - return sUIContentModeCAGravityLUT; -} - -static const _UIContentModeStringLUTEntry *UIContentModeDescriptionLUT(size_t *count) -{ - // Initialize this in a function (instead of at file level) to avoid - // startup initialization time. - static const _UIContentModeStringLUTEntry sUIContentModeDescriptionLUT[] = { - {UIViewContentModeScaleToFill, @"scaleToFill"}, - {UIViewContentModeScaleAspectFit, @"aspectFit"}, - {UIViewContentModeScaleAspectFill, @"aspectFill"}, - {UIViewContentModeRedraw, @"redraw"}, - {UIViewContentModeCenter, @"center"}, - {UIViewContentModeTop, @"top"}, - {UIViewContentModeBottom, @"bottom"}, - {UIViewContentModeLeft, @"left"}, - {UIViewContentModeRight, @"right"}, - {UIViewContentModeTopLeft, @"topLeft"}, - {UIViewContentModeTopRight, @"topRight"}, - {UIViewContentModeBottomLeft, @"bottomLeft"}, - {UIViewContentModeBottomRight, @"bottomRight"}, - }; - *count = sizeof(sUIContentModeDescriptionLUT) / sizeof(sUIContentModeDescriptionLUT[0]); - return sUIContentModeDescriptionLUT; -} - -NSString *ASDisplayNodeNSStringFromUIContentMode(UIViewContentMode contentMode) -{ - size_t lutSize; - const _UIContentModeStringLUTEntry *lut = UIContentModeDescriptionLUT(&lutSize); - for (size_t i = 0; i < lutSize; ++i) { - if (lut[i].contentMode == contentMode) { - return lut[i].string; - } - } - return [NSString stringWithFormat:@"%d", (int)contentMode]; -} - -UIViewContentMode ASDisplayNodeUIContentModeFromNSString(NSString *string) -{ - size_t lutSize; - const _UIContentModeStringLUTEntry *lut = UIContentModeDescriptionLUT(&lutSize); - for (size_t i = 0; i < lutSize; ++i) { - if (ASObjectIsEqual(lut[i].string, string)) { - return lut[i].contentMode; - } - } - return UIViewContentModeScaleToFill; -} - -NSString *const ASDisplayNodeCAContentsGravityFromUIContentMode(UIViewContentMode contentMode) -{ - size_t lutSize; - const _UIContentModeStringLUTEntry *lut = UIContentModeCAGravityLUT(&lutSize); - for (size_t i = 0; i < lutSize; ++i) { - if (lut[i].contentMode == contentMode) { - return lut[i].string; - } - } - ASDisplayNodeCAssert(contentMode == UIViewContentModeRedraw, @"Encountered an unknown contentMode %ld. Is this a new version of iOS?", (long)contentMode); - // Redraw is ok to return nil. - return nil; -} - -#define ContentModeCacheSize 10 -UIViewContentMode ASDisplayNodeUIContentModeFromCAContentsGravity(NSString *const contentsGravity) -{ - static int currentCacheIndex = 0; - static NSMutableArray *cachedStrings = [NSMutableArray arrayWithCapacity:ContentModeCacheSize]; - static UIViewContentMode cachedModes[ContentModeCacheSize] = {}; - - NSInteger foundCacheIndex = [cachedStrings indexOfObjectIdenticalTo:contentsGravity]; - if (foundCacheIndex != NSNotFound && foundCacheIndex < ContentModeCacheSize) { - return cachedModes[foundCacheIndex]; - } - - size_t lutSize; - const _UIContentModeStringLUTEntry *lut = UIContentModeCAGravityLUT(&lutSize); - for (size_t i = 0; i < lutSize; ++i) { - if (ASObjectIsEqual(lut[i].string, contentsGravity)) { - UIViewContentMode foundContentMode = lut[i].contentMode; - - if (currentCacheIndex < ContentModeCacheSize) { - // Cache the input value. This is almost always a different pointer than in our LUT and will frequently - // be the same value for an overwhelming majority of inputs. - [cachedStrings addObject:contentsGravity]; - cachedModes[currentCacheIndex] = foundContentMode; - currentCacheIndex++; - } - - return foundContentMode; - } - } - - ASDisplayNodeCAssert(contentsGravity, @"Encountered an unknown contentsGravity \"%@\". Is this a new version of iOS?", contentsGravity); - ASDisplayNodeCAssert(!contentsGravity, @"You passed nil to ASDisplayNodeUIContentModeFromCAContentsGravity. We're falling back to resize, but this is probably a bug."); - // If asserts disabled, fall back to this - return UIViewContentModeScaleToFill; -} - -BOOL ASDisplayNodeLayerHasAnimations(CALayer *layer) -{ - return (layer.animationKeys.count != 0); -} diff --git a/submodules/AsyncDisplayKit/Source/_ASDisplayLayer.mm b/submodules/AsyncDisplayKit/Source/_ASDisplayLayer.mm deleted file mode 100644 index 9ad2ca289b..0000000000 --- a/submodules/AsyncDisplayKit/Source/_ASDisplayLayer.mm +++ /dev/null @@ -1,212 +0,0 @@ -// -// _ASDisplayLayer.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#import - -#import -#import -#import -#import "ASDisplayNodeInternal.h" -#import -#import - -@implementation _ASDisplayLayer -{ - BOOL _attemptedDisplayWhileZeroSized; - - struct { - BOOL delegateDidChangeBounds:1; - } _delegateFlags; -} - -@dynamic displaysAsynchronously; - -#ifdef DEBUG -- (void)dealloc { - if (![NSThread isMainThread]) { - assert(true); - } -} -#endif - -#pragma mark - Properties - -- (void)setDelegate:(id)delegate -{ - [super setDelegate:delegate]; - _delegateFlags.delegateDidChangeBounds = [delegate respondsToSelector:@selector(layer:didChangeBoundsWithOldValue:newValue:)]; -} - -- (void)setDisplaySuspended:(BOOL)displaySuspended -{ - ASDisplayNodeAssertMainThread(); - if (_displaySuspended != displaySuspended) { - _displaySuspended = displaySuspended; - if (!displaySuspended) { - // If resuming display, trigger a display now. - [self setNeedsDisplay]; - } else { - // If suspending display, cancel any current async display so that we don't have contents set on us when it's finished. - [self cancelAsyncDisplay]; - } - } -} - -- (void)setBounds:(CGRect)bounds -{ - BOOL valid = ASDisplayNodeAssertNonFatal(ASIsCGRectValidForLayout(bounds), @"Caught attempt to set invalid bounds %@ on %@.", NSStringFromCGRect(bounds), self); - if (!valid) { - return; - } - if (_delegateFlags.delegateDidChangeBounds) { - CGRect oldBounds = self.bounds; - [super setBounds:bounds]; - self.asyncdisplaykit_node.threadSafeBounds = bounds; - [(id)self.delegate layer:self didChangeBoundsWithOldValue:oldBounds newValue:bounds]; - - } else { - [super setBounds:bounds]; - self.asyncdisplaykit_node.threadSafeBounds = bounds; - } - - if (_attemptedDisplayWhileZeroSized && CGRectIsEmpty(bounds) == NO && self.needsDisplayOnBoundsChange == NO) { - _attemptedDisplayWhileZeroSized = NO; - [self setNeedsDisplay]; - } -} - -#if DEBUG // These override is strictly to help detect application-level threading errors. Avoid method overhead in release. -- (void)setContents:(id)contents -{ - ASDisplayNodeAssertMainThread(); - [super setContents:contents]; -} - -- (void)setNeedsLayout -{ - ASDisplayNodeAssertMainThread(); - [super setNeedsLayout]; -} -#endif - -- (void)layoutSublayers -{ - ASDisplayNodeAssertMainThread(); - [super layoutSublayers]; - - [self.asyncdisplaykit_node __layout]; -} - -- (void)setNeedsDisplay -{ - ASDisplayNodeAssertMainThread(); - - // FIXME: Reconsider whether we should cancel a display in progress. - // We should definitely cancel a display that is scheduled, but unstarted display. - [self cancelAsyncDisplay]; - - // Short circuit if display is suspended. When resumed, we will setNeedsDisplay at that time. - if (!_displaySuspended) { - [super setNeedsDisplay]; - } -} - -#pragma mark - - -+ (dispatch_queue_t)displayQueue -{ - static dispatch_queue_t displayQueue = NULL; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - displayQueue = dispatch_queue_create("org.AsyncDisplayKit.ASDisplayLayer.displayQueue", DISPATCH_QUEUE_CONCURRENT); - // we use the highpri queue to prioritize UI rendering over other async operations - dispatch_set_target_queue(displayQueue, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)); - }); - - return displayQueue; -} - -+ (id)defaultValueForKey:(NSString *)key -{ - if ([key isEqualToString:@"displaysAsynchronously"]) { - return @YES; - } else if ([key isEqualToString:@"opaque"]) { - return @YES; - } else { - return [super defaultValueForKey:key]; - } -} - -#pragma mark - Display - -- (void)displayImmediately -{ - // This method is a low-level bypass that avoids touching CA, including any reset of the - // needsDisplay flag, until the .contents property is set with the result. - // It is designed to be able to block the thread of any caller and fully execute the display. - - ASDisplayNodeAssertMainThread(); - [self display:NO]; -} - -- (void)_hackResetNeedsDisplay -{ - ASDisplayNodeAssertMainThread(); - // Don't listen to our subclasses crazy ideas about setContents by going through super - super.contents = super.contents; -} - -- (void)display -{ - ASDisplayNodeAssertMainThread(); - [self _hackResetNeedsDisplay]; - - if (self.displaySuspended) { - return; - } - - [self display:self.displaysAsynchronously]; -} - -- (void)display:(BOOL)asynchronously -{ - if (CGRectIsEmpty(self.bounds)) { - _attemptedDisplayWhileZeroSized = YES; - } - - [self.asyncDelegate displayAsyncLayer:self asynchronously:asynchronously]; -} - -- (void)cancelAsyncDisplay -{ - ASDisplayNodeAssertMainThread(); - - [self.asyncDelegate cancelDisplayAsyncLayer:self]; -} - -// e.g. > -- (NSString *)description -{ - NSMutableString *description = [[super description] mutableCopy]; - ASDisplayNode *node = self.asyncdisplaykit_node; - if (node != nil) { - NSString *classString = [NSString stringWithFormat:@"%s-", object_getClassName(node)]; - [description replaceOccurrencesOfString:@"_ASDisplay" withString:classString options:kNilOptions range:NSMakeRange(0, description.length)]; - NSUInteger insertionIndex = [description rangeOfString:@">"].location; - if (insertionIndex != NSNotFound) { - NSString *nodeString = [NSString stringWithFormat:@"; node = %@", node]; - [description insertString:nodeString atIndex:insertionIndex]; - } - } - return description; -} - -@end diff --git a/submodules/AsyncDisplayKit/Source/_ASDisplayView.mm b/submodules/AsyncDisplayKit/Source/_ASDisplayView.mm deleted file mode 100644 index 365b141bbf..0000000000 --- a/submodules/AsyncDisplayKit/Source/_ASDisplayView.mm +++ /dev/null @@ -1,569 +0,0 @@ -// -// _ASDisplayView.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import "_ASDisplayViewAccessiblity.h" - -#import -#import -#import "ASDisplayNodeInternal.h" -#import -#import -#import -#import -#import -#import - -#pragma mark - _ASDisplayViewMethodOverrides - -typedef NS_OPTIONS(NSUInteger, _ASDisplayViewMethodOverrides) -{ - _ASDisplayViewMethodOverrideNone = 0, - _ASDisplayViewMethodOverrideCanBecomeFirstResponder = 1 << 0, - _ASDisplayViewMethodOverrideBecomeFirstResponder = 1 << 1, - _ASDisplayViewMethodOverrideCanResignFirstResponder = 1 << 2, - _ASDisplayViewMethodOverrideResignFirstResponder = 1 << 3, - _ASDisplayViewMethodOverrideIsFirstResponder = 1 << 4, -}; - -/** - * Returns _ASDisplayViewMethodOverrides for the given class - * - * @param c the class, required. - * - * @return _ASDisplayViewMethodOverrides. - */ -static _ASDisplayViewMethodOverrides GetASDisplayViewMethodOverrides(Class c) -{ - ASDisplayNodeCAssertNotNil(c, @"class is required"); - - _ASDisplayViewMethodOverrides overrides = _ASDisplayViewMethodOverrideNone; - if (ASSubclassOverridesSelector([_ASDisplayView class], c, @selector(canBecomeFirstResponder))) { - overrides |= _ASDisplayViewMethodOverrideCanBecomeFirstResponder; - } - if (ASSubclassOverridesSelector([_ASDisplayView class], c, @selector(becomeFirstResponder))) { - overrides |= _ASDisplayViewMethodOverrideBecomeFirstResponder; - } - if (ASSubclassOverridesSelector([_ASDisplayView class], c, @selector(canResignFirstResponder))) { - overrides |= _ASDisplayViewMethodOverrideCanResignFirstResponder; - } - if (ASSubclassOverridesSelector([_ASDisplayView class], c, @selector(resignFirstResponder))) { - overrides |= _ASDisplayViewMethodOverrideResignFirstResponder; - } - if (ASSubclassOverridesSelector([_ASDisplayView class], c, @selector(isFirstResponder))) { - overrides |= _ASDisplayViewMethodOverrideIsFirstResponder; - } - return overrides; -} - -#pragma mark - _ASDisplayView - -@interface _ASDisplayView () - -// Keep the node alive while its view is active. If you create a view, add its layer to a layer hierarchy, then release -// the view, the layer retains the view to prevent a crash. This replicates this behaviour for the node abstraction. -@property (nonatomic) ASDisplayNode *keepalive_node; -@end - -@implementation _ASDisplayView -{ - BOOL _inHitTest; - BOOL _inPointInside; - - NSArray *_accessibilityElements; - CGRect _lastAccessibilityElementsFrame; - - _ASDisplayViewMethodOverrides _methodOverrides; -} - -#pragma mark - Class - -+ (void)initialize -{ - __unused Class initializeSelf = self; - IMP staticInitialize = imp_implementationWithBlock(^(_ASDisplayView *view) { - ASDisplayNodeAssert(view.class == initializeSelf, @"View class %@ does not have a matching _staticInitialize method; check to ensure [super initialize] is called within any custom +initialize implementations! Overridden methods will not be called unless they are also implemented by superclass %@", view.class, initializeSelf); - view->_methodOverrides = GetASDisplayViewMethodOverrides(view.class); - }); - - class_replaceMethod(self, @selector(_staticInitialize), staticInitialize, "v:@"); -} - -+ (Class)layerClass -{ - return [_ASDisplayLayer class]; -} - -#pragma mark - NSObject Overrides - -- (instancetype)init -{ - if (!(self = [super init])) - return nil; - - [self _initializeInstance]; - - return self; -} - -- (void)_initializeInstance -{ - [self _staticInitialize]; -} - -- (void)_staticInitialize -{ - ASDisplayNodeAssert(NO, @"_staticInitialize must be overridden"); -} - -// e.g. ; frame = ...> -- (NSString *)description -{ - NSMutableString *description = [[super description] mutableCopy]; - - ASDisplayNode *node = _asyncdisplaykit_node; - - if (node != nil) { - NSString *classString = [NSString stringWithFormat:@"%s-", object_getClassName(node)]; - [description replaceOccurrencesOfString:@"_ASDisplay" withString:classString options:kNilOptions range:NSMakeRange(0, description.length)]; - NSUInteger semicolon = [description rangeOfString:@";"].location; - if (semicolon != NSNotFound) { - NSString *nodeString = [NSString stringWithFormat:@"; node = %@", node]; - [description insertString:nodeString atIndex:semicolon]; - } - // Remove layer description – it never contains valuable info and it duplicates the node info. Noisy. - NSRange layerDescriptionRange = [description rangeOfString:@"; layer = <.*>" options:NSRegularExpressionSearch]; - if (layerDescriptionRange.location != NSNotFound) { - [description replaceCharactersInRange:layerDescriptionRange withString:@""]; - // Our regex will grab the closing angle bracket and I'm not clever enough to come up with a better one, so re-add it if needed. - if ([description hasSuffix:@">"] == NO) { - [description appendString:@">"]; - } - } - } - return description; -} - -#pragma mark - UIView Overrides - -- (void)willMoveToWindow:(UIWindow *)newWindow -{ - ASDisplayNode *node = _asyncdisplaykit_node; // Create strong reference to weak ivar. - BOOL visible = (newWindow != nil); - if (visible && !node.inHierarchy) { - [node __enterHierarchy]; - } -} - -- (void)didMoveToWindow -{ - ASDisplayNode *node = _asyncdisplaykit_node; // Create strong reference to weak ivar. - BOOL visible = (self.window != nil); - if (!visible && node.inHierarchy) { - [node __exitHierarchy]; - } -} - -- (void)willMoveToSuperview:(UIView *)newSuperview -{ - // Keep the node alive while the view is in a view hierarchy. This helps ensure that async-drawing views can always - // display their contents as long as they are visible somewhere, and aids in lifecycle management because the - // lifecycle of the node can be treated as the same as the lifecycle of the view (let the view hierarchy own the - // view). - ASDisplayNode *node = _asyncdisplaykit_node; // Create strong reference to weak ivar. - UIView *currentSuperview = self.superview; - if (!currentSuperview && newSuperview) { - self.keepalive_node = node; - } - - if (newSuperview) { - ASDisplayNode *supernode = node.supernode; - BOOL supernodeLoaded = supernode.nodeLoaded; - ASDisplayNodeAssert(!supernode.isLayerBacked, @"Shouldn't be possible for _ASDisplayView's supernode to be layer-backed."); - - BOOL needsSupernodeUpdate = NO; - - if (supernode) { - if (supernodeLoaded) { - if (supernode.layerBacked) { - // See comment in -didMoveToSuperview. This case should be avoided, but is possible with app-level coding errors. - needsSupernodeUpdate = (supernode.layer != newSuperview.layer); - } else { - // If we have a supernode, compensate for users directly messing with views by hitching up to any new supernode. - needsSupernodeUpdate = (supernode.view != newSuperview); - } - } else { - needsSupernodeUpdate = YES; - } - } else { - // If we have no supernode and we are now in a view hierarchy, check to see if we can hook up to a supernode. - needsSupernodeUpdate = (newSuperview != nil); - } - - if (needsSupernodeUpdate) { - // -removeFromSupernode is called by -addSubnode:, if it is needed. - // FIXME: Needs rethinking if automaticallyManagesSubnodes=YES - [newSuperview.asyncdisplaykit_node _addSubnode:node]; - } - } -} - -- (void)didMoveToSuperview -{ - ASDisplayNode *node = _asyncdisplaykit_node; // Create strong reference to weak ivar. - UIView *superview = self.superview; - if (superview == nil) { - // Clearing keepalive_node may cause deallocation of the node. In this case, __exitHierarchy may not have an opportunity (e.g. _node will be cleared - // by the time -didMoveToWindow occurs after this) to clear the Visible interfaceState, which we need to do before deallocation to meet an API guarantee. - if (node.inHierarchy) { - [node __exitHierarchy]; - } - self.keepalive_node = nil; - } - -#ifndef MINIMAL_ASDK -#if DEBUG - // This is only to help detect issues when a root-of-view-controller node is reused separately from its view controller. - // Avoid overhead in release. - if (superview && node.viewControllerRoot) { - UIViewController *vc = [node closestViewController]; - - ASDisplayNodeAssert(vc != nil && [vc isKindOfClass:[ASViewController class]] && ((ASViewController*)vc).node == node, @"This node was once used as a view controller's node. You should not reuse it without its view controller."); - } -#endif -#endif - - ASDisplayNode *supernode = node.supernode; - ASDisplayNodeAssert(!supernode.isLayerBacked, @"Shouldn't be possible for superview's node to be layer-backed."); - - if (supernode) { - ASDisplayNodeAssertTrue(node.nodeLoaded); - BOOL supernodeLoaded = supernode.nodeLoaded; - BOOL needsSupernodeRemoval = NO; - - if (superview) { - // If our new superview is not the same as the supernode's view, or the supernode has no view, disconnect. - if (supernodeLoaded) { - if (supernode.layerBacked) { - // As asserted at the top, this shouldn't be possible, but in production with assertions disabled it can happen. - // We try to make such code behave as well as feasible because it's not that hard of an error to make if some deep - // child node of a layer-backed node happens to be view-backed, but it is not supported and should be avoided. - needsSupernodeRemoval = (supernode.layer != superview.layer); - } else { - needsSupernodeRemoval = (supernode.view != superview); - } - } else { - needsSupernodeRemoval = YES; - } - } else { - // If supernode is loaded but our superview is nil, the user likely manually removed us, so disconnect supernode. - // The unlikely alternative: we are in __unloadNode, with shouldRasterizeSubnodes just having been turned on. - // In the latter case, we don't want to disassemble the node hierarchy because all views are intentionally being destroyed. - BOOL nodeIsRasterized = ((node.hierarchyState & ASHierarchyStateRasterized) == ASHierarchyStateRasterized); - needsSupernodeRemoval = (supernodeLoaded && !nodeIsRasterized); - } - - if (needsSupernodeRemoval) { - // The node will only disconnect from its supernode, not removeFromSuperview, in this condition. - // FIXME: Needs rethinking if automaticallyManagesSubnodes=YES - [node _removeFromSupernode]; - } - } -} - -- (void)insertSubview:(UIView *)view atIndex:(NSInteger)index { - [super insertSubview:view atIndex:index]; - -#ifndef ASDK_ACCESSIBILITY_DISABLE - self.accessibilityElements = nil; -#endif -} - -- (void)addSubview:(UIView *)view -{ - [super addSubview:view]; - -#ifndef ASDK_ACCESSIBILITY_DISABLE - self.accessibilityElements = nil; -#endif -} - -- (void)willRemoveSubview:(UIView *)subview -{ - [super willRemoveSubview:subview]; - -#ifndef ASDK_ACCESSIBILITY_DISABLE - self.accessibilityElements = nil; -#endif -} - -- (CGSize)sizeThatFits:(CGSize)size -{ - ASDisplayNode *node = _asyncdisplaykit_node; // Create strong reference to weak ivar. - return node ? [node layoutThatFits:ASSizeRangeMake(size)].size : [super sizeThatFits:size]; -} - -- (void)setNeedsDisplay -{ - ASDisplayNodeAssertMainThread(); - // Standard implementation does not actually get to the layer, at least for views that don't implement drawRect:. - [self.layer setNeedsDisplay]; -} - -- (UIViewContentMode)contentMode -{ - return ASDisplayNodeUIContentModeFromCAContentsGravity(self.layer.contentsGravity); -} - -- (void)setContentMode:(UIViewContentMode)contentMode -{ - ASDisplayNodeAssert(contentMode != UIViewContentModeRedraw, @"Don't do this. Use needsDisplayOnBoundsChange instead."); - - // Do our own mapping so as not to call super and muck up needsDisplayOnBoundsChange. If we're in a production build, fall back to resize if we see redraw - self.layer.contentsGravity = (contentMode != UIViewContentModeRedraw) ? ASDisplayNodeCAContentsGravityFromUIContentMode(contentMode) : kCAGravityResize; -} - -- (void)setBounds:(CGRect)bounds -{ - ASDisplayNode *node = _asyncdisplaykit_node; // Create strong reference to weak ivar. - [super setBounds:bounds]; - node.threadSafeBounds = bounds; -} - -- (void)addGestureRecognizer:(UIGestureRecognizer *)gestureRecognizer -{ - [super addGestureRecognizer:gestureRecognizer]; - [_asyncdisplaykit_node nodeViewDidAddGestureRecognizer]; -} - -#pragma mark - Event Handling + UIResponder Overrides -- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event -{ - ASDisplayNode *node = _asyncdisplaykit_node; // Create strong reference to weak ivar. - if (node.methodOverrides & ASDisplayNodeMethodOverrideTouchesBegan) { - [node touchesBegan:touches withEvent:event]; - } else { - [super touchesBegan:touches withEvent:event]; - } -} - -- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event -{ - ASDisplayNode *node = _asyncdisplaykit_node; // Create strong reference to weak ivar. - if (node.methodOverrides & ASDisplayNodeMethodOverrideTouchesMoved) { - [node touchesMoved:touches withEvent:event]; - } else { - [super touchesMoved:touches withEvent:event]; - } -} - -- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event -{ - ASDisplayNode *node = _asyncdisplaykit_node; // Create strong reference to weak ivar. - if (node.methodOverrides & ASDisplayNodeMethodOverrideTouchesEnded) { - [node touchesEnded:touches withEvent:event]; - } else { - [super touchesEnded:touches withEvent:event]; - } -} - -- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event -{ - ASDisplayNode *node = _asyncdisplaykit_node; // Create strong reference to weak ivar. - if (node.methodOverrides & ASDisplayNodeMethodOverrideTouchesCancelled) { - [node touchesCancelled:touches withEvent:event]; - } else { - [super touchesCancelled:touches withEvent:event]; - } -} - -- (void)__forwardTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event -{ - [super touchesBegan:touches withEvent:event]; -} - -- (void)__forwardTouchesMoved:(NSSet *)touches withEvent:(UIEvent *)event -{ - [super touchesMoved:touches withEvent:event]; -} - -- (void)__forwardTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event -{ - [super touchesEnded:touches withEvent:event]; -} - -- (void)__forwardTouchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event -{ - [super touchesCancelled:touches withEvent:event]; -} - -- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event -{ - // REVIEW: We should optimize these types of messages by setting a boolean in the associated ASDisplayNode subclass if - // they actually override the method. Same goes for -pointInside:withEvent: below. Many UIKit classes use that - // pattern for meaningful reductions of message send overhead in hot code (especially event handling). - - // Set boolean so this method can be re-entrant. If the node subclass wants to default to / make use of UIView - // hitTest:, it will call it on the view, which is _ASDisplayView. After calling into the node, any additional calls - // should use the UIView implementation of hitTest: - ASDisplayNode *node = _asyncdisplaykit_node; // Create strong reference to weak ivar. - if (!_inHitTest) { - _inHitTest = YES; - UIView *hitView = [node hitTest:point withEvent:event]; - _inHitTest = NO; - return hitView; - } else { - return [super hitTest:point withEvent:event]; - } -} - -- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event -{ - // See comments in -hitTest:withEvent: for the strategy here. - ASDisplayNode *node = _asyncdisplaykit_node; // Create strong reference to weak ivar. - if (!_inPointInside) { - _inPointInside = YES; - BOOL result = [node pointInside:point withEvent:event]; - _inPointInside = NO; - return result; - } else { - return [super pointInside:point withEvent:event]; - } -} - -- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer -{ - ASDisplayNode *node = _asyncdisplaykit_node; // Create strong reference to weak ivar. - return [node gestureRecognizerShouldBegin:gestureRecognizer]; -} - -- (void)tintColorDidChange -{ - ASDisplayNode *node = _asyncdisplaykit_node; // Create strong reference to weak ivar. - [super tintColorDidChange]; - - [node tintColorDidChange]; -} - -#pragma mark UIResponder Handling - -#define IMPLEMENT_RESPONDER_METHOD(__sel, __nodeMethodOverride, __viewMethodOverride) \ -- (BOOL)__sel\ -{\ - ASDisplayNode *node = _asyncdisplaykit_node; /* Create strong reference to weak ivar. */ \ - /* Check if we can call through to ASDisplayNode subclass directly */ \ - if (node.methodOverrides & __nodeMethodOverride) { \ - return [node __sel]; \ - } else { \ - /* Prevent an infinite loop in here if [super __sel] was called on a \ - / _ASDisplayView subclass */ \ - if (self->_methodOverrides & __viewMethodOverride) { \ - /* Call through to views superclass as we expect super was called from the - _ASDisplayView subclass and a node subclass does not overwrite __sel */ \ - return [self __##__sel]; \ - } else { \ - /* Call through to internal node __sel method that will consider the view in responding */ \ - return [node __##__sel]; \ - } \ - } \ -}\ -/* All __ prefixed methods are called from ASDisplayNode to let the view decide in what UIResponder state they \ -are not overridden by a ASDisplayNode subclass */ \ -- (BOOL)__##__sel \ -{ \ - return [super __sel]; \ -} \ - -IMPLEMENT_RESPONDER_METHOD(canBecomeFirstResponder, - ASDisplayNodeMethodOverrideCanBecomeFirstResponder, - _ASDisplayViewMethodOverrideCanBecomeFirstResponder); -IMPLEMENT_RESPONDER_METHOD(becomeFirstResponder, - ASDisplayNodeMethodOverrideBecomeFirstResponder, - _ASDisplayViewMethodOverrideBecomeFirstResponder); -IMPLEMENT_RESPONDER_METHOD(canResignFirstResponder, - ASDisplayNodeMethodOverrideCanResignFirstResponder, - _ASDisplayViewMethodOverrideCanResignFirstResponder); -IMPLEMENT_RESPONDER_METHOD(resignFirstResponder, - ASDisplayNodeMethodOverrideResignFirstResponder, - _ASDisplayViewMethodOverrideResignFirstResponder); -IMPLEMENT_RESPONDER_METHOD(isFirstResponder, - ASDisplayNodeMethodOverrideIsFirstResponder, - _ASDisplayViewMethodOverrideIsFirstResponder); - -- (BOOL)canPerformAction:(SEL)action withSender:(id)sender -{ - // We forward responder-chain actions to our node if we can't handle them ourselves. See -targetForAction:withSender:. - ASDisplayNode *node = _asyncdisplaykit_node; // Create strong reference to weak ivar. - return ([super canPerformAction:action withSender:sender] || [node respondsToSelector:action]); -} - -- (void)layoutMarginsDidChange -{ - ASDisplayNode *node = _asyncdisplaykit_node; // Create strong reference to weak ivar. - [super layoutMarginsDidChange]; - - [node layoutMarginsDidChange]; -} - -- (void)safeAreaInsetsDidChange -{ - ASDisplayNode *node = _asyncdisplaykit_node; // Create strong reference to weak ivar. - [super safeAreaInsetsDidChange]; - - [node safeAreaInsetsDidChange]; -} - -- (id)forwardingTargetForSelector:(SEL)aSelector -{ - // Ideally, we would implement -targetForAction:withSender: and simply return the node where we don't respond personally. - // Unfortunately UIResponder's default implementation of -targetForAction:withSender: doesn't follow its own documentation. It doesn't call -targetForAction:withSender: up the responder chain when -canPerformAction:withSender: fails, but instead merely calls -canPerformAction:withSender: on itself and then up the chain. rdar://20111500. - // Consequently, to forward responder-chain actions to our node, we override -canPerformAction:withSender: (used by the chain) to indicate support for responder chain-driven actions that our node supports, and then provide the node as a forwarding target here. - ASDisplayNode *node = _asyncdisplaykit_node; // Create strong reference to weak ivar. - return node; -} - -#if TARGET_OS_TV -#pragma mark - tvOS -- (BOOL)canBecomeFocused -{ - ASDisplayNode *node = _asyncdisplaykit_node; // Create strong reference to weak ivar. - return [node canBecomeFocused]; -} - -- (void)didUpdateFocusInContext:(UIFocusUpdateContext *)context withAnimationCoordinator:(UIFocusAnimationCoordinator *)coordinator -{ - ASDisplayNode *node = _asyncdisplaykit_node; // Create strong reference to weak ivar. - return [node didUpdateFocusInContext:context withAnimationCoordinator:coordinator]; -} - -- (void)setNeedsFocusUpdate -{ - ASDisplayNode *node = _asyncdisplaykit_node; // Create strong reference to weak ivar. - return [node setNeedsFocusUpdate]; -} - -- (void)updateFocusIfNeeded -{ - ASDisplayNode *node = _asyncdisplaykit_node; // Create strong reference to weak ivar. - return [node updateFocusIfNeeded]; -} - -- (BOOL)shouldUpdateFocusInContext:(UIFocusUpdateContext *)context -{ - ASDisplayNode *node = _asyncdisplaykit_node; // Create strong reference to weak ivar. - return [node shouldUpdateFocusInContext:context]; -} - -- (UIView *)preferredFocusedView -{ - ASDisplayNode *node = _asyncdisplaykit_node; // Create strong reference to weak ivar. - return [node preferredFocusedView]; -} -#endif -@end diff --git a/submodules/AsyncDisplayKit/Source/_ASDisplayViewAccessiblity.h b/submodules/AsyncDisplayKit/Source/_ASDisplayViewAccessiblity.h deleted file mode 100644 index 9d0bf0719a..0000000000 --- a/submodules/AsyncDisplayKit/Source/_ASDisplayViewAccessiblity.h +++ /dev/null @@ -1,17 +0,0 @@ -// -// _ASDisplayViewAccessiblity.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import - -// WARNING: When dealing with accessibility elements, please use the `accessibilityElements` -// property instead of the older methods e.g. `accessibilityElementCount()`. While the older methods -// should still work as long as accessibility is enabled, this framework provides no guarantees on -// their correctness. For details, see -// https://developer.apple.com/documentation/objectivec/nsobject/1615147-accessibilityelements diff --git a/submodules/AsyncDisplayKit/Source/_ASDisplayViewAccessiblity.mm b/submodules/AsyncDisplayKit/Source/_ASDisplayViewAccessiblity.mm deleted file mode 100644 index b24e8e9f58..0000000000 --- a/submodules/AsyncDisplayKit/Source/_ASDisplayViewAccessiblity.mm +++ /dev/null @@ -1,349 +0,0 @@ -// -// _ASDisplayViewAccessiblity.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef ASDK_ACCESSIBILITY_DISABLE - -#import -#import -#import -#import -#import "ASDisplayNodeInternal.h" - -#import - -NS_INLINE UIAccessibilityTraits InteractiveAccessibilityTraitsMask() { - return UIAccessibilityTraitLink | UIAccessibilityTraitKeyboardKey | UIAccessibilityTraitButton; -} - -#pragma mark - UIAccessibilityElement - -@protocol ASAccessibilityElementPositioning - -@property (nonatomic, readonly) CGRect accessibilityFrame; - -@end - -typedef NSComparisonResult (^SortAccessibilityElementsComparator)(id, id); - -/// Sort accessiblity elements first by y and than by x origin. -static void SortAccessibilityElements(NSMutableArray *elements) -{ - ASDisplayNodeCAssertNotNil(elements, @"Should pass in a NSMutableArray"); - - static SortAccessibilityElementsComparator comparator = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - comparator = ^NSComparisonResult(id a, id b) { - CGPoint originA = a.accessibilityFrame.origin; - CGPoint originB = b.accessibilityFrame.origin; - if (originA.y == originB.y) { - if (originA.x == originB.x) { - return NSOrderedSame; - } - return (originA.x < originB.x) ? NSOrderedAscending : NSOrderedDescending; - } - return (originA.y < originB.y) ? NSOrderedAscending : NSOrderedDescending; - }; - }); - [elements sortUsingComparator:comparator]; -} - -@interface ASAccessibilityElement : UIAccessibilityElement - -@property (nonatomic) ASDisplayNode *node; -@property (nonatomic) ASDisplayNode *containerNode; - -+ (ASAccessibilityElement *)accessibilityElementWithContainer:(UIView *)container node:(ASDisplayNode *)node containerNode:(ASDisplayNode *)containerNode; - -@end - -@implementation ASAccessibilityElement - -+ (ASAccessibilityElement *)accessibilityElementWithContainer:(UIView *)container node:(ASDisplayNode *)node containerNode:(ASDisplayNode *)containerNode -{ - ASAccessibilityElement *accessibilityElement = [[ASAccessibilityElement alloc] initWithAccessibilityContainer:container]; - accessibilityElement.node = node; - accessibilityElement.containerNode = containerNode; - accessibilityElement.accessibilityIdentifier = node.accessibilityIdentifier; - accessibilityElement.accessibilityLabel = node.accessibilityLabel; - accessibilityElement.accessibilityHint = node.accessibilityHint; - accessibilityElement.accessibilityValue = node.accessibilityValue; - accessibilityElement.accessibilityTraits = node.accessibilityTraits; -#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_11_0 - if (AS_AVAILABLE_IOS_TVOS(11, 11)) { - accessibilityElement.accessibilityAttributedLabel = node.accessibilityAttributedLabel; - accessibilityElement.accessibilityAttributedHint = node.accessibilityAttributedHint; - accessibilityElement.accessibilityAttributedValue = node.accessibilityAttributedValue; - } -#endif - return accessibilityElement; -} - -- (CGRect)accessibilityFrame -{ - CGRect accessibilityFrame = [self.containerNode convertRect:self.node.bounds fromNode:self.node]; - accessibilityFrame = UIAccessibilityConvertFrameToScreenCoordinates(accessibilityFrame, self.accessibilityContainer); - return accessibilityFrame; -} - -@end - -#pragma mark - _ASDisplayView / UIAccessibilityContainer - -@interface ASAccessibilityCustomAction : UIAccessibilityCustomAction - -@property (nonatomic) UIView *container; -@property (nonatomic) ASDisplayNode *node; -@property (nonatomic) ASDisplayNode *containerNode; - -@end - -@implementation ASAccessibilityCustomAction - -- (CGRect)accessibilityFrame -{ - CGRect accessibilityFrame = [self.containerNode convertRect:self.node.bounds fromNode:self.node]; - accessibilityFrame = UIAccessibilityConvertFrameToScreenCoordinates(accessibilityFrame, self.container); - return accessibilityFrame; -} - -@end - -/// Collect all subnodes for the given node by walking down the subnode tree and calculates the screen coordinates based on the containerNode and container -static void CollectUIAccessibilityElementsForNode(ASDisplayNode *node, ASDisplayNode *containerNode, id container, NSMutableArray *elements) -{ - ASDisplayNodeCAssertNotNil(elements, @"Should pass in a NSMutableArray"); - - ASDisplayNodePerformBlockOnEveryNodeBFS(node, ^(ASDisplayNode * _Nonnull currentNode) { - // For every subnode that is layer backed or it's supernode has subtree rasterization enabled - // we have to create a UIAccessibilityElement as no view for this node exists - if (currentNode != containerNode && currentNode.isAccessibilityElement) { - UIAccessibilityElement *accessibilityElement = [ASAccessibilityElement accessibilityElementWithContainer:container node:currentNode containerNode:containerNode]; - [elements addObject:accessibilityElement]; - } - }); -} - -static void CollectAccessibilityElementsForContainer(ASDisplayNode *container, UIView *view, NSMutableArray *elements) { - UIAccessibilityElement *accessiblityElement = [ASAccessibilityElement accessibilityElementWithContainer:view node:container containerNode:container]; - - NSMutableArray *labeledNodes = [[NSMutableArray alloc] init]; - NSMutableArray *actions = [[NSMutableArray alloc] init]; - std::queue queue; - queue.push(container); - - // If the container does not have an accessibility label set, or if the label is meant for custom - // actions only, then aggregate its subnodes' labels. Otherwise, treat the label as an overriden - // value and do not perform the aggregation. - BOOL shouldAggregateSubnodeLabels = - (container.accessibilityLabel.length == 0) || - (container.accessibilityTraits & InteractiveAccessibilityTraitsMask()); - - ASDisplayNode *node = nil; - while (!queue.empty()) { - node = queue.front(); - queue.pop(); - - if (node != container && node.isAccessibilityContainer) { - CollectAccessibilityElementsForContainer(node, view, elements); - continue; - } - - if (node.accessibilityLabel.length > 0) { - if (node.accessibilityTraits & InteractiveAccessibilityTraitsMask()) { - ASAccessibilityCustomAction *action = [[ASAccessibilityCustomAction alloc] initWithName:node.accessibilityLabel target:node selector:@selector(performAccessibilityCustomAction:)]; - action.node = node; - action.containerNode = node.supernode; - action.container = node.supernode.view; - [actions addObject:action]; - } else if (node == container || shouldAggregateSubnodeLabels) { - // Even though not surfaced to UIKit, create a non-interactive element for purposes of building sorted aggregated label. - ASAccessibilityElement *nonInteractiveElement = [ASAccessibilityElement accessibilityElementWithContainer:view node:node containerNode:container]; - [labeledNodes addObject:nonInteractiveElement]; - } - } - - for (ASDisplayNode *subnode in node.subnodes) { - queue.push(subnode); - } - } - - SortAccessibilityElements(labeledNodes); - -#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_11_0 - if (AS_AVAILABLE_IOS_TVOS(11, 11)) { - NSArray *attributedLabels = [labeledNodes valueForKey:@"accessibilityAttributedLabel"]; - NSMutableAttributedString *attributedLabel = [NSMutableAttributedString new]; - [attributedLabels enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { - if (idx != 0) { - [attributedLabel appendAttributedString:[[NSAttributedString alloc] initWithString:@", "]]; - } - [attributedLabel appendAttributedString:(NSAttributedString *)obj]; - }]; - accessiblityElement.accessibilityAttributedLabel = attributedLabel; - } else -#endif - { - NSArray *labels = [labeledNodes valueForKey:@"accessibilityLabel"]; - accessiblityElement.accessibilityLabel = [labels componentsJoinedByString:@", "]; - } - - SortAccessibilityElements(actions); - accessiblityElement.accessibilityCustomActions = actions; - - [elements addObject:accessiblityElement]; -} - -/// Collect all accessibliity elements for a given view and view node -static void CollectAccessibilityElementsForView(UIView *view, NSMutableArray *elements) -{ - ASDisplayNodeCAssertNotNil(elements, @"Should pass in a NSMutableArray"); - - ASDisplayNode *node = view.asyncdisplaykit_node; - - static Class displayListViewClass = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - displayListViewClass = NSClassFromString(@"Display.ListView"); - }); - BOOL anySubNodeIsCollection = (nil != ASDisplayNodeFindFirstNode(node, - ^BOOL(ASDisplayNode *nodeToCheck) { - if (displayListViewClass != nil && [nodeToCheck isKindOfClass:displayListViewClass]) { - return true; - } - return false; - /*return ASDynamicCast(nodeToCheck, ASCollectionNode) != nil || - ASDynamicCast(nodeToCheck, ASTableNode) != nil;*/ - })); - - if (node.isAccessibilityContainer && !anySubNodeIsCollection) { - CollectAccessibilityElementsForContainer(node, view, elements); - return; - } - - // Handle rasterize case - if (node.rasterizesSubtree) { - CollectUIAccessibilityElementsForNode(node, node, view, elements); - return; - } - - for (ASDisplayNode *subnode in node.subnodes) { - if (subnode.isAccessibilityElement) { - - // An accessiblityElement can either be a UIView or a UIAccessibilityElement - if (subnode.isLayerBacked) { - // No view for layer backed nodes exist. It's necessary to create a UIAccessibilityElement that represents this node - UIAccessibilityElement *accessiblityElement = [ASAccessibilityElement accessibilityElementWithContainer:view node:subnode containerNode:node]; - [elements addObject:accessiblityElement]; - } else { - // Accessiblity element is not layer backed just add the view as accessibility element - [elements addObject:subnode.view]; - } - } else if (subnode.isLayerBacked) { - // Go down the hierarchy of the layer backed subnode and collect all of the UIAccessibilityElement - CollectUIAccessibilityElementsForNode(subnode, node, view, elements); - } else if ([subnode accessibilityElementCount] > 0) { - // UIView is itself a UIAccessibilityContainer just add it - [elements addObject:subnode.view]; - } - } -} - -@interface _ASDisplayView () { - NSArray *_accessibilityElements; -} - -@end - -@implementation _ASDisplayView (UIAccessibilityContainer) - -- (void)accessibilityElementDidBecomeFocused { - ASDisplayNode *viewNode = self.asyncdisplaykit_node; - if ([viewNode respondsToSelector:@selector(accessibilityElementDidBecomeFocused)]) { - [viewNode accessibilityElementDidBecomeFocused]; - } -} - -/*- (bool)accessibilityActivate { - ASDisplayNode *viewNode = self.asyncdisplaykit_node; - if ([viewNode respondsToSelector:@selector(accessibilityActivate)]) { - return [viewNode accessibilityActivate]; - } - return false; -}*/ - -#pragma mark - UIAccessibility - -- (void)setAccessibilityElements:(NSArray *)accessibilityElements -{ - ASDisplayNodeAssertMainThread(); - _accessibilityElements = nil; -} - -- (NSArray *)accessibilityElements -{ - ASDisplayNodeAssertMainThread(); - - ASDisplayNode *viewNode = self.asyncdisplaykit_node; - if (viewNode == nil) { - return @[]; - } - if (true || _accessibilityElements == nil) { - _accessibilityElements = [viewNode accessibilityElements]; - } - return _accessibilityElements; -} - -@end - -@implementation ASDisplayNode (AccessibilityInternal) - -- (NSArray *)accessibilityElements -{ - if (!self.isNodeLoaded) { - ASDisplayNodeFailAssert(@"Cannot access accessibilityElements since node is not loaded"); - return @[]; - } - NSMutableArray *accessibilityElements = [[NSMutableArray alloc] init]; - CollectAccessibilityElementsForView(self.view, accessibilityElements); - SortAccessibilityElements(accessibilityElements); - return accessibilityElements; -} - -@end - -@implementation _ASDisplayView (UIAccessibilityAction) - -- (BOOL)accessibilityActivate { - return [self.asyncdisplaykit_node accessibilityActivate]; -} - -- (void)accessibilityIncrement { - [self.asyncdisplaykit_node accessibilityIncrement]; -} - -- (void)accessibilityDecrement { - [self.asyncdisplaykit_node accessibilityDecrement]; -} - -- (BOOL)accessibilityScroll:(UIAccessibilityScrollDirection)direction { - return [self.asyncdisplaykit_node accessibilityScroll:direction]; -} - -- (BOOL)accessibilityPerformEscape { - return [self.asyncdisplaykit_node accessibilityPerformEscape]; -} - -- (BOOL)accessibilityPerformMagicTap { - return [self.asyncdisplaykit_node accessibilityPerformMagicTap]; -} - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/Source/_ASPendingState.h b/submodules/AsyncDisplayKit/Source/_ASPendingState.h deleted file mode 100644 index 0a96e7a8ad..0000000000 --- a/submodules/AsyncDisplayKit/Source/_ASPendingState.h +++ /dev/null @@ -1,41 +0,0 @@ -// -// _ASPendingState.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#import - -/** - - Private header for ASDisplayNode.mm - - _ASPendingState is a proxy for a UIView that has yet to be created. - In response to its setters, it sets an internal property and a flag that indicates that that property has been set. - - When you want to configure a view from this pending state information, just call -applyToView: - */ - -@interface _ASPendingState : NSObject - -// Supports all of the properties included in the ASDisplayNodeViewProperties protocol - -- (void)applyToView:(UIView *)view withSpecialPropertiesHandling:(BOOL)setFrameDirectly; -- (void)applyToLayer:(CALayer *)layer; - -+ (_ASPendingState *)pendingViewStateFromLayer:(CALayer *)layer; -+ (_ASPendingState *)pendingViewStateFromView:(UIView *)view; - -@property (nonatomic, readonly) BOOL hasSetNeedsLayout; -@property (nonatomic, readonly) BOOL hasSetNeedsDisplay; - -@property (nonatomic, readonly) BOOL hasChanges; - -- (void)clearChanges; - -@end diff --git a/submodules/AsyncDisplayKit/Source/_ASPendingState.mm b/submodules/AsyncDisplayKit/Source/_ASPendingState.mm deleted file mode 100644 index 5bf75e50d1..0000000000 --- a/submodules/AsyncDisplayKit/Source/_ASPendingState.mm +++ /dev/null @@ -1,1379 +0,0 @@ -// -// _ASPendingState.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import "_ASPendingState.h" - -#import -#import -#import -#import -#import "ASDisplayNodeInternal.h" -#import - -#define __shouldSetNeedsDisplay(layer) (flags.needsDisplay \ - || (flags.setOpaque && opaque != (layer).opaque)\ - || (flags.setBackgroundColor && !CGColorEqualToColor(backgroundColor, (layer).backgroundColor))) - -typedef struct { - // Properties - int needsDisplay:1; - int needsLayout:1; - int layoutIfNeeded:1; - - // Flags indicating that a given property should be applied to the view at creation - int setClipsToBounds:1; - int setOpaque:1; - int setNeedsDisplayOnBoundsChange:1; - int setAutoresizesSubviews:1; - int setAutoresizingMask:1; - int setFrame:1; - int setBounds:1; - int setBackgroundColor:1; - int setTintColor:1; - int setHidden:1; - int setAlpha:1; - int setCornerRadius:1; - int setContentMode:1; - int setNeedsDisplay:1; - int setAnchorPoint:1; - int setPosition:1; - int setZPosition:1; - int setTransform:1; - int setSublayerTransform:1; - int setContents:1; - int setContentsGravity:1; - int setContentsRect:1; - int setContentsCenter:1; - int setContentsScale:1; - int setRasterizationScale:1; - int setUserInteractionEnabled:1; - int setExclusiveTouch:1; - int setShadowColor:1; - int setShadowOpacity:1; - int setShadowOffset:1; - int setShadowRadius:1; - int setBorderWidth:1; - int setBorderColor:1; - int setAsyncTransactionContainer:1; - int setAllowsGroupOpacity:1; - int setAllowsEdgeAntialiasing:1; - int setEdgeAntialiasingMask:1; - int setIsAccessibilityElement:1; - int setAccessibilityLabel:1; - int setAccessibilityAttributedLabel:1; - int setAccessibilityHint:1; - int setAccessibilityAttributedHint:1; - int setAccessibilityValue:1; - int setAccessibilityAttributedValue:1; - int setAccessibilityTraits:1; - int setAccessibilityFrame:1; - int setAccessibilityLanguage:1; - int setAccessibilityElementsHidden:1; - int setAccessibilityViewIsModal:1; - int setShouldGroupAccessibilityChildren:1; - int setAccessibilityIdentifier:1; - int setAccessibilityNavigationStyle:1; - int setAccessibilityHeaderElements:1; - int setAccessibilityActivationPoint:1; - int setAccessibilityPath:1; - int setSemanticContentAttribute:1; - int setLayoutMargins:1; - int setPreservesSuperviewLayoutMargins:1; - int setInsetsLayoutMarginsFromSafeArea:1; - int setAccessibilityCustomActions:1; -} ASPendingStateFlags; - -@implementation _ASPendingState -{ - @package //Expose all ivars for ASDisplayNode to bypass getters for efficiency - - UIViewAutoresizing autoresizingMask; - unsigned int edgeAntialiasingMask; - CGRect frame; // Frame is only to be used for synchronous views wrapped by nodes (see setFrame:) - CGRect bounds; - CGColorRef backgroundColor; - CGFloat alpha; - CGFloat cornerRadius; - UIViewContentMode contentMode; - CGPoint anchorPoint; - CGPoint position; - CGFloat zPosition; - CATransform3D transform; - CATransform3D sublayerTransform; - id contents; - NSString *contentsGravity; - CGRect contentsRect; - CGRect contentsCenter; - CGFloat contentsScale; - CGFloat rasterizationScale; - CGColorRef shadowColor; - CGFloat shadowOpacity; - CGSize shadowOffset; - CGFloat shadowRadius; - CGFloat borderWidth; - CGColorRef borderColor; - BOOL asyncTransactionContainer; - UIEdgeInsets layoutMargins; - BOOL preservesSuperviewLayoutMargins; - BOOL insetsLayoutMarginsFromSafeArea; - BOOL isAccessibilityElement; - NSString *accessibilityLabel; - NSAttributedString *accessibilityAttributedLabel; - NSString *accessibilityHint; - NSAttributedString *accessibilityAttributedHint; - NSString *accessibilityValue; - NSAttributedString *accessibilityAttributedValue; - UIAccessibilityTraits accessibilityTraits; - CGRect accessibilityFrame; - NSString *accessibilityLanguage; - BOOL accessibilityElementsHidden; - BOOL accessibilityViewIsModal; - BOOL shouldGroupAccessibilityChildren; - NSString *accessibilityIdentifier; - UIAccessibilityNavigationStyle accessibilityNavigationStyle; - NSArray *accessibilityHeaderElements; - CGPoint accessibilityActivationPoint; - UIBezierPath *accessibilityPath; - UISemanticContentAttribute semanticContentAttribute API_AVAILABLE(ios(9.0), tvos(9.0)); - NSArray * accessibilityCustomActions; - - ASPendingStateFlags _flags; -} - -/** - * Apply the state's frame, bounds, and position to layer. This will not - * be called on synchronous view-backed nodes which require we directly - * call [view setFrame:]. - * - * FIXME: How should we reconcile order-of-operations between setting frame, bounds, position? - * Note we can't read bounds and position in the background, so we have to keep the frame - * value intact until application time (now). - */ -ASDISPLAYNODE_INLINE void ASPendingStateApplyMetricsToLayer(_ASPendingState *state, CALayer *layer) { - ASPendingStateFlags flags = state->_flags; - if (flags.setFrame) { - CGRect _bounds = CGRectZero; - CGPoint _position = CGPointZero; - ASBoundsAndPositionForFrame(state->frame, layer.bounds.origin, layer.anchorPoint, &_bounds, &_position); - layer.bounds = _bounds; - layer.position = _position; - } else { - if (flags.setBounds) - layer.bounds = state->bounds; - if (flags.setPosition) - layer.position = state->position; - } -} - -@synthesize clipsToBounds=clipsToBounds; -@synthesize opaque=opaque; -@synthesize frame=frame; -@synthesize bounds=bounds; -@synthesize backgroundColor=backgroundColor; -@synthesize hidden=isHidden; -@synthesize needsDisplayOnBoundsChange=needsDisplayOnBoundsChange; -@synthesize allowsGroupOpacity=allowsGroupOpacity; -@synthesize allowsEdgeAntialiasing=allowsEdgeAntialiasing; -@synthesize edgeAntialiasingMask=edgeAntialiasingMask; -@synthesize autoresizesSubviews=autoresizesSubviews; -@synthesize autoresizingMask=autoresizingMask; -@synthesize tintColor=tintColor; -@synthesize alpha=alpha; -@synthesize cornerRadius=cornerRadius; -@synthesize contentMode=contentMode; -@synthesize anchorPoint=anchorPoint; -@synthesize position=position; -@synthesize zPosition=zPosition; -@synthesize transform=transform; -@synthesize sublayerTransform=sublayerTransform; -@synthesize contents=contents; -@synthesize contentsGravity=contentsGravity; -@synthesize contentsRect=contentsRect; -@synthesize contentsCenter=contentsCenter; -@synthesize contentsScale=contentsScale; -@synthesize rasterizationScale=rasterizationScale; -@synthesize userInteractionEnabled=userInteractionEnabled; -@synthesize exclusiveTouch=exclusiveTouch; -@synthesize shadowColor=shadowColor; -@synthesize shadowOpacity=shadowOpacity; -@synthesize shadowOffset=shadowOffset; -@synthesize shadowRadius=shadowRadius; -@synthesize borderWidth=borderWidth; -@synthesize borderColor=borderColor; -@synthesize asyncdisplaykit_asyncTransactionContainer=asyncTransactionContainer; -@synthesize semanticContentAttribute=semanticContentAttribute; -@synthesize layoutMargins=layoutMargins; -@synthesize preservesSuperviewLayoutMargins=preservesSuperviewLayoutMargins; -@synthesize insetsLayoutMarginsFromSafeArea=insetsLayoutMarginsFromSafeArea; - -static CGColorRef blackColorRef = NULL; -static UIColor *defaultTintColor = nil; - -- (instancetype)init -{ - if (!(self = [super init])) - return nil; - - - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - // Default UIKit color is an RGB color - CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); - blackColorRef = CGColorCreate(colorSpace, (CGFloat[]){0,0,0,1} ); - CFRetain(blackColorRef); - CGColorSpaceRelease(colorSpace); - defaultTintColor = [UIColor colorWithRed:0.0 green:0.478 blue:1.0 alpha:1.0]; - }); - - // Set defaults, these come from the defaults specified in CALayer and UIView - clipsToBounds = NO; - opaque = YES; - frame = CGRectZero; - bounds = CGRectZero; - backgroundColor = nil; - tintColor = defaultTintColor; - isHidden = NO; - needsDisplayOnBoundsChange = NO; - allowsGroupOpacity = ASDefaultAllowsGroupOpacity(); - allowsEdgeAntialiasing = ASDefaultAllowsEdgeAntialiasing(); - autoresizesSubviews = YES; - alpha = 1.0f; - cornerRadius = 0.0f; - contentMode = UIViewContentModeScaleToFill; - _flags.needsDisplay = NO; - anchorPoint = CGPointMake(0.5, 0.5); - position = CGPointZero; - zPosition = 0.0; - transform = CATransform3DIdentity; - sublayerTransform = CATransform3DIdentity; - contents = nil; - contentsGravity = kCAGravityResize; - contentsRect = CGRectMake(0.0f, 0.0f, 1.0f, 1.0f); - contentsCenter = CGRectMake(0.0f, 0.0f, 1.0f, 1.0f); - contentsScale = 1.0f; - rasterizationScale = 1.0f; - userInteractionEnabled = YES; - shadowColor = blackColorRef; - shadowOpacity = 0.0; - shadowOffset = CGSizeMake(0, -3); - shadowRadius = 3; - borderWidth = 0; - borderColor = blackColorRef; - layoutMargins = UIEdgeInsetsMake(8, 8, 8, 8); - preservesSuperviewLayoutMargins = NO; - insetsLayoutMarginsFromSafeArea = YES; - isAccessibilityElement = NO; - accessibilityLabel = nil; - accessibilityAttributedLabel = nil; - accessibilityHint = nil; - accessibilityAttributedHint = nil; - accessibilityValue = nil; - accessibilityAttributedValue = nil; - accessibilityTraits = UIAccessibilityTraitNone; - accessibilityFrame = CGRectZero; - accessibilityLanguage = nil; - accessibilityElementsHidden = NO; - accessibilityViewIsModal = NO; - shouldGroupAccessibilityChildren = NO; - accessibilityIdentifier = nil; - accessibilityNavigationStyle = UIAccessibilityNavigationStyleAutomatic; - accessibilityHeaderElements = nil; - accessibilityActivationPoint = CGPointZero; - accessibilityPath = nil; - edgeAntialiasingMask = (kCALayerLeftEdge | kCALayerRightEdge | kCALayerTopEdge | kCALayerBottomEdge); - semanticContentAttribute = UISemanticContentAttributeUnspecified; - - return self; -} - -- (void)setNeedsDisplay -{ - _flags.needsDisplay = YES; -} - -- (void)setNeedsLayout -{ - _flags.needsLayout = YES; -} - -- (void)layoutIfNeeded -{ - _flags.layoutIfNeeded = YES; -} - -- (void)setClipsToBounds:(BOOL)flag -{ - clipsToBounds = flag; - _flags.setClipsToBounds = YES; -} - -- (void)setOpaque:(BOOL)flag -{ - opaque = flag; - _flags.setOpaque = YES; -} - -- (void)setNeedsDisplayOnBoundsChange:(BOOL)flag -{ - needsDisplayOnBoundsChange = flag; - _flags.setNeedsDisplayOnBoundsChange = YES; -} - -- (void)setAllowsGroupOpacity:(BOOL)flag -{ - allowsGroupOpacity = flag; - _flags.setAllowsGroupOpacity = YES; -} - -- (void)setAllowsEdgeAntialiasing:(BOOL)flag -{ - allowsEdgeAntialiasing = flag; - _flags.setAllowsEdgeAntialiasing = YES; -} - -- (void)setEdgeAntialiasingMask:(unsigned int)mask -{ - edgeAntialiasingMask = mask; - _flags.setEdgeAntialiasingMask = YES; -} - -- (void)setAutoresizesSubviews:(BOOL)flag -{ - autoresizesSubviews = flag; - _flags.setAutoresizesSubviews = YES; -} - -- (void)setAutoresizingMask:(UIViewAutoresizing)mask -{ - autoresizingMask = mask; - _flags.setAutoresizingMask = YES; -} - -- (void)setFrame:(CGRect)newFrame -{ - frame = newFrame; - _flags.setFrame = YES; -} - -- (void)setBounds:(CGRect)newBounds -{ - ASDisplayNodeAssert(!isnan(newBounds.size.width) && !isnan(newBounds.size.height), @"Invalid bounds %@ provided to %@", NSStringFromCGRect(newBounds), self); - if (isnan(newBounds.size.width)) - newBounds.size.width = 0.0; - if (isnan(newBounds.size.height)) - newBounds.size.height = 0.0; - bounds = newBounds; - _flags.setBounds = YES; -} - -- (CGColorRef)backgroundColor -{ - return backgroundColor; -} - -- (void)setBackgroundColor:(CGColorRef)color -{ - if (color == backgroundColor) { - return; - } - - CGColorRelease(backgroundColor); - backgroundColor = CGColorRetain(color); - _flags.setBackgroundColor = YES; -} - -- (void)setTintColor:(UIColor *)newTintColor -{ - tintColor = newTintColor; - _flags.setTintColor = YES; -} - -- (void)setHidden:(BOOL)flag -{ - isHidden = flag; - _flags.setHidden = YES; -} - -- (void)setAlpha:(CGFloat)newAlpha -{ - alpha = newAlpha; - _flags.setAlpha = YES; -} - -- (void)setCornerRadius:(CGFloat)newCornerRadius -{ - cornerRadius = newCornerRadius; - _flags.setCornerRadius = YES; -} - -- (void)setContentMode:(UIViewContentMode)newContentMode -{ - contentMode = newContentMode; - _flags.setContentMode = YES; -} - -- (void)setAnchorPoint:(CGPoint)newAnchorPoint -{ - anchorPoint = newAnchorPoint; - _flags.setAnchorPoint = YES; -} - -- (void)setPosition:(CGPoint)newPosition -{ - ASDisplayNodeAssert(!isnan(newPosition.x) && !isnan(newPosition.y), @"Invalid position %@ provided to %@", NSStringFromCGPoint(newPosition), self); - if (isnan(newPosition.x)) - newPosition.x = 0.0; - if (isnan(newPosition.y)) - newPosition.y = 0.0; - position = newPosition; - _flags.setPosition = YES; -} - -- (void)setZPosition:(CGFloat)newPosition -{ - zPosition = newPosition; - _flags.setZPosition = YES; -} - -- (void)setTransform:(CATransform3D)newTransform -{ - transform = newTransform; - _flags.setTransform = YES; -} - -- (void)setSublayerTransform:(CATransform3D)newSublayerTransform -{ - sublayerTransform = newSublayerTransform; - _flags.setSublayerTransform = YES; -} - -- (void)setContents:(id)newContents -{ - if (contents == newContents) { - return; - } - - contents = newContents; - _flags.setContents = YES; -} - -- (void)setContentsGravity:(NSString *)newContentsGravity -{ - contentsGravity = newContentsGravity; - _flags.setContentsGravity = YES; -} - -- (void)setContentsRect:(CGRect)newContentsRect -{ - contentsRect = newContentsRect; - _flags.setContentsRect = YES; -} - -- (void)setContentsCenter:(CGRect)newContentsCenter -{ - contentsCenter = newContentsCenter; - _flags.setContentsCenter = YES; -} - -- (void)setContentsScale:(CGFloat)newContentsScale -{ - contentsScale = newContentsScale; - _flags.setContentsScale = YES; -} - -- (void)setRasterizationScale:(CGFloat)newRasterizationScale -{ - rasterizationScale = newRasterizationScale; - _flags.setRasterizationScale = YES; -} - -- (void)setUserInteractionEnabled:(BOOL)flag -{ - userInteractionEnabled = flag; - _flags.setUserInteractionEnabled = YES; -} - -- (void)setExclusiveTouch:(BOOL)flag -{ - exclusiveTouch = flag; - _flags.setExclusiveTouch = YES; -} - -- (void)setShadowColor:(CGColorRef)color -{ - if (shadowColor == color) { - return; - } - - if (shadowColor != blackColorRef) { - CGColorRelease(shadowColor); - } - shadowColor = color; - CGColorRetain(shadowColor); - - _flags.setShadowColor = YES; -} - -- (void)setShadowOpacity:(CGFloat)newOpacity -{ - shadowOpacity = newOpacity; - _flags.setShadowOpacity = YES; -} - -- (void)setShadowOffset:(CGSize)newOffset -{ - shadowOffset = newOffset; - _flags.setShadowOffset = YES; -} - -- (void)setShadowRadius:(CGFloat)newRadius -{ - shadowRadius = newRadius; - _flags.setShadowRadius = YES; -} - -- (void)setBorderWidth:(CGFloat)newWidth -{ - borderWidth = newWidth; - _flags.setBorderWidth = YES; -} - -- (void)setBorderColor:(CGColorRef)color -{ - if (borderColor == color) { - return; - } - - if (borderColor != blackColorRef) { - CGColorRelease(borderColor); - } - borderColor = color; - CGColorRetain(borderColor); - - _flags.setBorderColor = YES; -} - -- (void)asyncdisplaykit_setAsyncTransactionContainer:(BOOL)flag -{ - asyncTransactionContainer = flag; - _flags.setAsyncTransactionContainer = YES; -} - -- (void)setLayoutMargins:(UIEdgeInsets)margins -{ - layoutMargins = margins; - _flags.setLayoutMargins = YES; -} - -- (void)setPreservesSuperviewLayoutMargins:(BOOL)flag -{ - preservesSuperviewLayoutMargins = flag; - _flags.setPreservesSuperviewLayoutMargins = YES; -} - -- (void)setInsetsLayoutMarginsFromSafeArea:(BOOL)flag -{ - insetsLayoutMarginsFromSafeArea = flag; - _flags.setInsetsLayoutMarginsFromSafeArea = YES; -} - -- (void)setSemanticContentAttribute:(UISemanticContentAttribute)attribute API_AVAILABLE(ios(9.0), tvos(9.0)) { - semanticContentAttribute = attribute; - _flags.setSemanticContentAttribute = YES; -} - -- (void)setAccessibilityCustomActions:(NSArray *)accessibilityCustomActions { - self->accessibilityCustomActions = accessibilityCustomActions; - _flags.setAccessibilityCustomActions = YES; -} - -- (BOOL)isAccessibilityElement -{ - return isAccessibilityElement; -} - -- (void)setIsAccessibilityElement:(BOOL)newIsAccessibilityElement -{ - isAccessibilityElement = newIsAccessibilityElement; - _flags.setIsAccessibilityElement = YES; -} - -- (NSString *)accessibilityLabel -{ - if (_flags.setAccessibilityAttributedLabel) { - return accessibilityAttributedLabel.string; - } - return accessibilityLabel; -} - -- (void)setAccessibilityLabel:(NSString *)newAccessibilityLabel -{ - ASCompareAssignCopy(accessibilityLabel, newAccessibilityLabel); - _flags.setAccessibilityLabel = YES; - _flags.setAccessibilityAttributedLabel = NO; -} - -- (NSAttributedString *)accessibilityAttributedLabel -{ - if (_flags.setAccessibilityLabel) { - return [[NSAttributedString alloc] initWithString:accessibilityLabel]; - } - return accessibilityAttributedLabel; -} - -- (void)setAccessibilityAttributedLabel:(NSAttributedString *)newAccessibilityAttributedLabel -{ - ASCompareAssignCopy(accessibilityAttributedLabel, newAccessibilityAttributedLabel); - _flags.setAccessibilityAttributedLabel = YES; - _flags.setAccessibilityLabel = NO; -} - -- (NSString *)accessibilityHint -{ - if (_flags.setAccessibilityAttributedHint) { - return accessibilityAttributedHint.string; - } - return accessibilityHint; -} - -- (void)setAccessibilityHint:(NSString *)newAccessibilityHint -{ - ASCompareAssignCopy(accessibilityHint, newAccessibilityHint); - _flags.setAccessibilityHint = YES; - _flags.setAccessibilityAttributedHint = NO; -} - -- (NSAttributedString *)accessibilityAttributedHint -{ - if (_flags.setAccessibilityHint) { - return [[NSAttributedString alloc] initWithString:accessibilityHint]; - } - return accessibilityAttributedHint; -} - -- (void)setAccessibilityAttributedHint:(NSAttributedString *)newAccessibilityAttributedHint -{ - ASCompareAssignCopy(accessibilityAttributedHint, newAccessibilityAttributedHint); - _flags.setAccessibilityAttributedHint = YES; - _flags.setAccessibilityHint = NO; -} - -- (NSString *)accessibilityValue -{ - if (_flags.setAccessibilityAttributedValue) { - return accessibilityAttributedValue.string; - } - return accessibilityValue; -} - -- (void)setAccessibilityValue:(NSString *)newAccessibilityValue -{ - ASCompareAssignCopy(accessibilityValue, newAccessibilityValue); - _flags.setAccessibilityValue = YES; - _flags.setAccessibilityAttributedValue = NO; -} - -- (NSAttributedString *)accessibilityAttributedValue -{ - if (_flags.setAccessibilityValue) { - return [[NSAttributedString alloc] initWithString:accessibilityValue]; - } - return accessibilityAttributedValue; -} - -- (void)setAccessibilityAttributedValue:(NSAttributedString *)newAccessibilityAttributedValue -{ - ASCompareAssignCopy(accessibilityAttributedValue, newAccessibilityAttributedValue); - _flags.setAccessibilityAttributedValue = YES; - _flags.setAccessibilityValue = NO; -} - -- (UIAccessibilityTraits)accessibilityTraits -{ - return accessibilityTraits; -} - -- (void)setAccessibilityTraits:(UIAccessibilityTraits)newAccessibilityTraits -{ - accessibilityTraits = newAccessibilityTraits; - _flags.setAccessibilityTraits = YES; -} - -- (CGRect)accessibilityFrame -{ - return accessibilityFrame; -} - -- (void)setAccessibilityFrame:(CGRect)newAccessibilityFrame -{ - accessibilityFrame = newAccessibilityFrame; - _flags.setAccessibilityFrame = YES; -} - -- (NSString *)accessibilityLanguage -{ - return accessibilityLanguage; -} - -- (void)setAccessibilityLanguage:(NSString *)newAccessibilityLanguage -{ - _flags.setAccessibilityLanguage = YES; - accessibilityLanguage = newAccessibilityLanguage; -} - -- (BOOL)accessibilityElementsHidden -{ - return accessibilityElementsHidden; -} - -- (void)setAccessibilityElementsHidden:(BOOL)newAccessibilityElementsHidden -{ - accessibilityElementsHidden = newAccessibilityElementsHidden; - _flags.setAccessibilityElementsHidden = YES; -} - -- (BOOL)accessibilityViewIsModal -{ - return accessibilityViewIsModal; -} - -- (void)setAccessibilityViewIsModal:(BOOL)newAccessibilityViewIsModal -{ - accessibilityViewIsModal = newAccessibilityViewIsModal; - _flags.setAccessibilityViewIsModal = YES; -} - -- (BOOL)shouldGroupAccessibilityChildren -{ - return shouldGroupAccessibilityChildren; -} - -- (void)setShouldGroupAccessibilityChildren:(BOOL)newShouldGroupAccessibilityChildren -{ - shouldGroupAccessibilityChildren = newShouldGroupAccessibilityChildren; - _flags.setShouldGroupAccessibilityChildren = YES; -} - -- (NSString *)accessibilityIdentifier -{ - return accessibilityIdentifier; -} - -- (void)setAccessibilityIdentifier:(NSString *)newAccessibilityIdentifier -{ - _flags.setAccessibilityIdentifier = YES; - if (accessibilityIdentifier != newAccessibilityIdentifier) { - accessibilityIdentifier = [newAccessibilityIdentifier copy]; - } -} - -- (UIAccessibilityNavigationStyle)accessibilityNavigationStyle -{ - return accessibilityNavigationStyle; -} - -- (void)setAccessibilityNavigationStyle:(UIAccessibilityNavigationStyle)newAccessibilityNavigationStyle -{ - _flags.setAccessibilityNavigationStyle = YES; - accessibilityNavigationStyle = newAccessibilityNavigationStyle; -} - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-implementations" -- (NSArray *)accessibilityHeaderElements -{ - return accessibilityHeaderElements; -} - -- (void)setAccessibilityHeaderElements:(NSArray *)newAccessibilityHeaderElements -{ - _flags.setAccessibilityHeaderElements = YES; - if (accessibilityHeaderElements != newAccessibilityHeaderElements) { - accessibilityHeaderElements = [newAccessibilityHeaderElements copy]; - } -} -#pragma clang diagnostic pop - -- (CGPoint)accessibilityActivationPoint -{ - if (_flags.setAccessibilityActivationPoint) { - return accessibilityActivationPoint; - } - - // Default == Mid-point of the accessibilityFrame - return CGPointMake(CGRectGetMidX(accessibilityFrame), CGRectGetMidY(accessibilityFrame)); -} - -- (void)setAccessibilityActivationPoint:(CGPoint)newAccessibilityActivationPoint -{ - _flags.setAccessibilityActivationPoint = YES; - accessibilityActivationPoint = newAccessibilityActivationPoint; -} - -- (UIBezierPath *)accessibilityPath -{ - return accessibilityPath; -} - -- (void)setAccessibilityPath:(UIBezierPath *)newAccessibilityPath -{ - _flags.setAccessibilityPath = YES; - if (accessibilityPath != newAccessibilityPath) { - accessibilityPath = newAccessibilityPath; - } -} - -- (void)applyToLayer:(CALayer *)layer -{ - ASPendingStateFlags flags = _flags; - - if (__shouldSetNeedsDisplay(layer)) { - [layer setNeedsDisplay]; - } - - if (flags.setAnchorPoint) - layer.anchorPoint = anchorPoint; - - if (flags.setZPosition) - layer.zPosition = zPosition; - - if (flags.setTransform) - layer.transform = transform; - - if (flags.setSublayerTransform) - layer.sublayerTransform = sublayerTransform; - - if (flags.setClipsToBounds) - layer.masksToBounds = clipsToBounds; - - if (flags.setBackgroundColor) - layer.backgroundColor = backgroundColor; - - if (flags.setOpaque) - layer.opaque = opaque; - - if (flags.setHidden) - layer.hidden = isHidden; - - if (flags.setAlpha) - layer.opacity = alpha; - - if (flags.setCornerRadius) - layer.cornerRadius = cornerRadius; - - if (flags.setContentMode) - layer.contentsGravity = ASDisplayNodeCAContentsGravityFromUIContentMode(contentMode); - - if (flags.setShadowColor) - layer.shadowColor = shadowColor; - - if (flags.setShadowOpacity) - layer.shadowOpacity = shadowOpacity; - - if (flags.setShadowOffset) - layer.shadowOffset = shadowOffset; - - if (flags.setShadowRadius) - layer.shadowRadius = shadowRadius; - - if (flags.setBorderWidth) - layer.borderWidth = borderWidth; - - if (flags.setBorderColor) - layer.borderColor = borderColor; - - if (flags.setNeedsDisplayOnBoundsChange) - layer.needsDisplayOnBoundsChange = needsDisplayOnBoundsChange; - - if (flags.setAllowsGroupOpacity) - layer.allowsGroupOpacity = allowsGroupOpacity; - - if (flags.setAllowsEdgeAntialiasing) - layer.allowsEdgeAntialiasing = allowsEdgeAntialiasing; - - if (flags.setEdgeAntialiasingMask) - layer.edgeAntialiasingMask = edgeAntialiasingMask; - - if (flags.setAsyncTransactionContainer) - layer.asyncdisplaykit_asyncTransactionContainer = asyncTransactionContainer; - - if (flags.setOpaque) - ASDisplayNodeAssert(layer.opaque == opaque, @"Didn't set opaque as desired"); - - ASPendingStateApplyMetricsToLayer(self, layer); - - if (flags.setContents) - layer.contents = contents; - - if (flags.setContentsScale) - layer.contentsScale = contentsScale; - - if (flags.setRasterizationScale) - layer.rasterizationScale = rasterizationScale; - - if (flags.setContentsGravity) - layer.contentsGravity = contentsGravity; - - if (flags.setContentsRect) - layer.contentsRect = contentsRect; - - if (flags.setContentsCenter) - layer.contentsCenter = contentsCenter; - - if (flags.needsLayout) - [layer setNeedsLayout]; - - if (flags.layoutIfNeeded) - [layer layoutIfNeeded]; -} - -- (void)applyToView:(UIView *)view withSpecialPropertiesHandling:(BOOL)specialPropertiesHandling -{ - /* - Use our convenience setters blah here instead of layer.blah - We were accidentally setting some properties on layer here, but view in UIViewBridgeOptimizations. - - That could easily cause bugs where it mattered whether you set something up on a bg thread on in -didLoad - because a different setter would be called. - */ - - CALayer *layer = view.layer; - - ASPendingStateFlags flags = _flags; - if (__shouldSetNeedsDisplay(layer)) { - [view setNeedsDisplay]; - } - - if (flags.setAnchorPoint) - layer.anchorPoint = anchorPoint; - - if (flags.setPosition) - layer.position = position; - - if (flags.setZPosition) - layer.zPosition = zPosition; - - if (flags.setBounds) - view.bounds = bounds; - - if (flags.setTransform) - layer.transform = transform; - - if (flags.setSublayerTransform) - layer.sublayerTransform = sublayerTransform; - - if (flags.setClipsToBounds) - view.clipsToBounds = clipsToBounds; - - if (flags.setBackgroundColor) { - // We have to make sure certain nodes get the background color call directly set - if (specialPropertiesHandling) { - view.backgroundColor = [UIColor colorWithCGColor:backgroundColor]; - } else { - // Set the background color to the layer as in the UIView bridge we use this value as background color - layer.backgroundColor = backgroundColor; - } - } - - if (flags.setTintColor) - view.tintColor = self.tintColor; - - if (flags.setOpaque) - layer.opaque = opaque; - - if (flags.setHidden) - view.hidden = isHidden; - - if (flags.setAlpha) - view.alpha = alpha; - - if (flags.setCornerRadius) - layer.cornerRadius = cornerRadius; - - if (flags.setContentMode) - view.contentMode = contentMode; - - if (flags.setUserInteractionEnabled) - view.userInteractionEnabled = userInteractionEnabled; - - #if TARGET_OS_IOS - if (flags.setExclusiveTouch) - view.exclusiveTouch = exclusiveTouch; - #endif - - if (flags.setShadowColor) - layer.shadowColor = shadowColor; - - if (flags.setShadowOpacity) - layer.shadowOpacity = shadowOpacity; - - if (flags.setShadowOffset) - layer.shadowOffset = shadowOffset; - - if (flags.setShadowRadius) - layer.shadowRadius = shadowRadius; - - if (flags.setBorderWidth) - layer.borderWidth = borderWidth; - - if (flags.setBorderColor) - layer.borderColor = borderColor; - - if (flags.setAutoresizingMask) - view.autoresizingMask = autoresizingMask; - - if (flags.setAutoresizesSubviews) - view.autoresizesSubviews = autoresizesSubviews; - - if (flags.setNeedsDisplayOnBoundsChange) - layer.needsDisplayOnBoundsChange = needsDisplayOnBoundsChange; - - if (flags.setAllowsGroupOpacity) - layer.allowsGroupOpacity = allowsGroupOpacity; - - if (flags.setAllowsEdgeAntialiasing) - layer.allowsEdgeAntialiasing = allowsEdgeAntialiasing; - - if (flags.setEdgeAntialiasingMask) - layer.edgeAntialiasingMask = edgeAntialiasingMask; - - if (flags.setAsyncTransactionContainer) - view.asyncdisplaykit_asyncTransactionContainer = asyncTransactionContainer; - - if (flags.setOpaque) - ASDisplayNodeAssert(layer.opaque == opaque, @"Didn't set opaque as desired"); - - if (flags.setLayoutMargins) - view.layoutMargins = layoutMargins; - - if (flags.setPreservesSuperviewLayoutMargins) - view.preservesSuperviewLayoutMargins = preservesSuperviewLayoutMargins; - - if (AS_AVAILABLE_IOS(11.0)) { - if (flags.setInsetsLayoutMarginsFromSafeArea) { - view.insetsLayoutMarginsFromSafeArea = insetsLayoutMarginsFromSafeArea; - } - } - - if (flags.setAccessibilityCustomActions) { - view.accessibilityCustomActions = accessibilityCustomActions; - } - - if (flags.setSemanticContentAttribute) { - view.semanticContentAttribute = semanticContentAttribute; - } - - if (flags.setIsAccessibilityElement) - view.isAccessibilityElement = isAccessibilityElement; - - if (flags.setAccessibilityLabel) - view.accessibilityLabel = accessibilityLabel; - - if (flags.setAccessibilityHint) - view.accessibilityHint = accessibilityHint; - - if (flags.setAccessibilityValue) - view.accessibilityValue = accessibilityValue; - - if (AS_AVAILABLE_IOS(11)) { - if (flags.setAccessibilityAttributedLabel) { - view.accessibilityAttributedLabel = accessibilityAttributedLabel; - } - if (flags.setAccessibilityAttributedHint) { - view.accessibilityAttributedHint = accessibilityAttributedHint; - } - if (flags.setAccessibilityAttributedValue) { - view.accessibilityAttributedValue = accessibilityAttributedValue; - } - } - - if (flags.setAccessibilityTraits) - view.accessibilityTraits = accessibilityTraits; - - if (flags.setAccessibilityFrame) - view.accessibilityFrame = accessibilityFrame; - - if (flags.setAccessibilityLanguage) - view.accessibilityLanguage = accessibilityLanguage; - - if (flags.setAccessibilityElementsHidden) - view.accessibilityElementsHidden = accessibilityElementsHidden; - - if (flags.setAccessibilityViewIsModal) - view.accessibilityViewIsModal = accessibilityViewIsModal; - - if (flags.setShouldGroupAccessibilityChildren) - view.shouldGroupAccessibilityChildren = shouldGroupAccessibilityChildren; - - if (flags.setAccessibilityIdentifier) - view.accessibilityIdentifier = accessibilityIdentifier; - - if (flags.setAccessibilityNavigationStyle) - view.accessibilityNavigationStyle = accessibilityNavigationStyle; - -#if TARGET_OS_TV - if (flags.setAccessibilityHeaderElements) - view.accessibilityHeaderElements = accessibilityHeaderElements; -#endif - - if (flags.setAccessibilityActivationPoint) - view.accessibilityActivationPoint = accessibilityActivationPoint; - - if (flags.setAccessibilityPath) - view.accessibilityPath = accessibilityPath; - - if (flags.setFrame && specialPropertiesHandling) { - // Frame is only defined when transform is identity because we explicitly diverge from CALayer behavior and define frame without transform -//#if DEBUG -// // Checking if the transform is identity is expensive, so disable when unnecessary. We have assertions on in Release, so DEBUG is the only way I know of. -// ASDisplayNodeAssert(CATransform3DIsIdentity(layer.transform), @"-[ASDisplayNode setFrame:] - self.transform must be identity in order to set the frame property. (From Apple's UIView documentation: If the transform property is not the identity transform, the value of this property is undefined and therefore should be ignored.)"); -//#endif - view.frame = frame; - } else { - ASPendingStateApplyMetricsToLayer(self, layer); - } - - if (flags.setContents) - layer.contents = contents; - - if (flags.setContentsGravity) - layer.contentsGravity = contentsGravity; - - if (flags.setContentsRect) - layer.contentsRect = contentsRect; - - if (flags.setContentsCenter) - layer.contentsCenter = contentsCenter; - - if (flags.setContentsScale) - layer.contentsScale = contentsScale; - - if (flags.setRasterizationScale) - layer.rasterizationScale = rasterizationScale; - - if (flags.needsLayout) - [view setNeedsLayout]; - - if (flags.layoutIfNeeded) - [view layoutIfNeeded]; -} - -// FIXME: Make this more efficient by tracking which properties are set rather than reading everything. -+ (_ASPendingState *)pendingViewStateFromLayer:(CALayer *)layer -{ - if (!layer) { - return nil; - } - _ASPendingState *pendingState = [[_ASPendingState alloc] init]; - pendingState.anchorPoint = layer.anchorPoint; - pendingState.position = layer.position; - pendingState.zPosition = layer.zPosition; - pendingState.bounds = layer.bounds; - pendingState.transform = layer.transform; - pendingState.sublayerTransform = layer.sublayerTransform; - pendingState.contents = layer.contents; - pendingState.contentsGravity = layer.contentsGravity; - pendingState.contentsRect = layer.contentsRect; - pendingState.contentsCenter = layer.contentsCenter; - pendingState.contentsScale = layer.contentsScale; - pendingState.rasterizationScale = layer.rasterizationScale; - pendingState.clipsToBounds = layer.masksToBounds; - pendingState.backgroundColor = layer.backgroundColor; - pendingState.opaque = layer.opaque; - pendingState.hidden = layer.hidden; - pendingState.alpha = layer.opacity; - pendingState.cornerRadius = layer.cornerRadius; - pendingState.contentMode = ASDisplayNodeUIContentModeFromCAContentsGravity(layer.contentsGravity); - pendingState.shadowColor = layer.shadowColor; - pendingState.shadowOpacity = layer.shadowOpacity; - pendingState.shadowOffset = layer.shadowOffset; - pendingState.shadowRadius = layer.shadowRadius; - pendingState.borderWidth = layer.borderWidth; - pendingState.borderColor = layer.borderColor; - pendingState.needsDisplayOnBoundsChange = layer.needsDisplayOnBoundsChange; - pendingState.allowsGroupOpacity = layer.allowsGroupOpacity; - pendingState.allowsEdgeAntialiasing = layer.allowsEdgeAntialiasing; - pendingState.edgeAntialiasingMask = layer.edgeAntialiasingMask; - return pendingState; -} - -// FIXME: Make this more efficient by tracking which properties are set rather than reading everything. -+ (_ASPendingState *)pendingViewStateFromView:(UIView *)view -{ - if (!view) { - return nil; - } - _ASPendingState *pendingState = [[_ASPendingState alloc] init]; - - CALayer *layer = view.layer; - pendingState.anchorPoint = layer.anchorPoint; - pendingState.position = layer.position; - pendingState.zPosition = layer.zPosition; - pendingState.bounds = view.bounds; - pendingState.transform = layer.transform; - pendingState.sublayerTransform = layer.sublayerTransform; - pendingState.contents = layer.contents; - pendingState.contentsGravity = layer.contentsGravity; - pendingState.contentsRect = layer.contentsRect; - pendingState.contentsCenter = layer.contentsCenter; - pendingState.contentsScale = layer.contentsScale; - pendingState.rasterizationScale = layer.rasterizationScale; - pendingState.clipsToBounds = view.clipsToBounds; - pendingState.backgroundColor = layer.backgroundColor; - pendingState.tintColor = view.tintColor; - pendingState.opaque = layer.opaque; - pendingState.hidden = view.hidden; - pendingState.alpha = view.alpha; - pendingState.cornerRadius = layer.cornerRadius; - pendingState.contentMode = view.contentMode; - pendingState.userInteractionEnabled = view.userInteractionEnabled; -#if TARGET_OS_IOS - pendingState.exclusiveTouch = view.exclusiveTouch; -#endif - pendingState.shadowColor = layer.shadowColor; - pendingState.shadowOpacity = layer.shadowOpacity; - pendingState.shadowOffset = layer.shadowOffset; - pendingState.shadowRadius = layer.shadowRadius; - pendingState.borderWidth = layer.borderWidth; - pendingState.borderColor = layer.borderColor; - pendingState.autoresizingMask = view.autoresizingMask; - pendingState.autoresizesSubviews = view.autoresizesSubviews; - pendingState.needsDisplayOnBoundsChange = layer.needsDisplayOnBoundsChange; - pendingState.allowsGroupOpacity = layer.allowsGroupOpacity; - pendingState.allowsEdgeAntialiasing = layer.allowsEdgeAntialiasing; - pendingState.edgeAntialiasingMask = layer.edgeAntialiasingMask; - pendingState.semanticContentAttribute = view.semanticContentAttribute; - pendingState.layoutMargins = view.layoutMargins; - pendingState.preservesSuperviewLayoutMargins = view.preservesSuperviewLayoutMargins; - if (AS_AVAILABLE_IOS(11)) { - pendingState.insetsLayoutMarginsFromSafeArea = view.insetsLayoutMarginsFromSafeArea; - } - pendingState.isAccessibilityElement = view.isAccessibilityElement; - pendingState.accessibilityLabel = view.accessibilityLabel; - pendingState.accessibilityHint = view.accessibilityHint; - pendingState.accessibilityValue = view.accessibilityValue; -#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_11_0 - if (AS_AVAILABLE_IOS_TVOS(11, 11)) { - pendingState.accessibilityAttributedLabel = view.accessibilityAttributedLabel; - pendingState.accessibilityAttributedHint = view.accessibilityAttributedHint; - pendingState.accessibilityAttributedValue = view.accessibilityAttributedValue; - } -#endif - pendingState.accessibilityTraits = view.accessibilityTraits; - pendingState.accessibilityFrame = view.accessibilityFrame; - pendingState.accessibilityLanguage = view.accessibilityLanguage; - pendingState.accessibilityElementsHidden = view.accessibilityElementsHidden; - pendingState.accessibilityViewIsModal = view.accessibilityViewIsModal; - pendingState.shouldGroupAccessibilityChildren = view.shouldGroupAccessibilityChildren; - pendingState.accessibilityIdentifier = view.accessibilityIdentifier; - pendingState.accessibilityNavigationStyle = view.accessibilityNavigationStyle; -#if TARGET_OS_TV - pendingState.accessibilityHeaderElements = view.accessibilityHeaderElements; -#endif - pendingState.accessibilityActivationPoint = view.accessibilityActivationPoint; - pendingState.accessibilityPath = view.accessibilityPath; - return pendingState; -} - -- (void)clearChanges -{ - _flags = (ASPendingStateFlags){ 0 }; -} - -- (BOOL)hasSetNeedsLayout -{ - return _flags.needsLayout; -} - -- (BOOL)hasSetNeedsDisplay -{ - return _flags.needsDisplay; -} - -- (BOOL)hasChanges -{ - ASPendingStateFlags flags = _flags; - - return (flags.setAnchorPoint - || flags.setPosition - || flags.setZPosition - || flags.setFrame - || flags.setBounds - || flags.setPosition - || flags.setTransform - || flags.setSublayerTransform - || flags.setContents - || flags.setContentsGravity - || flags.setContentsRect - || flags.setContentsCenter - || flags.setContentsScale - || flags.setRasterizationScale - || flags.setClipsToBounds - || flags.setBackgroundColor - || flags.setTintColor - || flags.setHidden - || flags.setAlpha - || flags.setCornerRadius - || flags.setContentMode - || flags.setUserInteractionEnabled - || flags.setExclusiveTouch - || flags.setShadowOpacity - || flags.setShadowOffset - || flags.setShadowRadius - || flags.setShadowColor - || flags.setBorderWidth - || flags.setBorderColor - || flags.setAutoresizingMask - || flags.setAutoresizesSubviews - || flags.setNeedsDisplayOnBoundsChange - || flags.setAllowsGroupOpacity - || flags.setAllowsEdgeAntialiasing - || flags.setEdgeAntialiasingMask - || flags.needsDisplay - || flags.needsLayout - || flags.setAsyncTransactionContainer - || flags.setOpaque - || flags.setSemanticContentAttribute - || flags.setLayoutMargins - || flags.setPreservesSuperviewLayoutMargins - || flags.setInsetsLayoutMarginsFromSafeArea - || flags.setIsAccessibilityElement - || flags.setAccessibilityLabel - || flags.setAccessibilityAttributedLabel - || flags.setAccessibilityHint - || flags.setAccessibilityAttributedHint - || flags.setAccessibilityValue - || flags.setAccessibilityAttributedValue - || flags.setAccessibilityTraits - || flags.setAccessibilityFrame - || flags.setAccessibilityLanguage - || flags.setAccessibilityElementsHidden - || flags.setAccessibilityViewIsModal - || flags.setShouldGroupAccessibilityChildren - || flags.setAccessibilityIdentifier - || flags.setAccessibilityNavigationStyle - || flags.setAccessibilityHeaderElements - || flags.setAccessibilityActivationPoint - || flags.setAccessibilityPath); -} - -- (void)dealloc -{ - CGColorRelease(backgroundColor); - - if (shadowColor != blackColorRef) { - CGColorRelease(shadowColor); - } - - if (borderColor != blackColorRef) { - CGColorRelease(borderColor); - } -} - -@end diff --git a/submodules/AsyncDisplayKit/Source/_ASScopeTimer.h b/submodules/AsyncDisplayKit/Source/_ASScopeTimer.h deleted file mode 100644 index 523599dd0a..0000000000 --- a/submodules/AsyncDisplayKit/Source/_ASScopeTimer.h +++ /dev/null @@ -1,56 +0,0 @@ -// -// _ASScopeTimer.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#pragma once - -/** - Must compile as c++ for this to work. - - Usage: - // Can be an ivar or local variable - NSTimeInterval placeToStoreTiming; - - { - // some scope - AS::ScopeTimer t(placeToStoreTiming); - DoPotentiallySlowWork(); - MorePotentiallySlowWork(); - } - - */ - -namespace AS { - struct ScopeTimer { - NSTimeInterval begin; - NSTimeInterval &outT; - ScopeTimer(NSTimeInterval &outRef) : outT(outRef) { - begin = CACurrentMediaTime(); - } - ~ScopeTimer() { - outT = CACurrentMediaTime() - begin; - } - }; - - // variant where repeated calls are summed - struct SumScopeTimer { - NSTimeInterval begin; - NSTimeInterval &outT; - BOOL enable; - SumScopeTimer(NSTimeInterval &outRef, BOOL enable = YES) : outT(outRef), enable(enable) { - if (enable) { - begin = CACurrentMediaTime(); - } - } - ~SumScopeTimer() { - if (enable) { - outT += CACurrentMediaTime() - begin; - } - } - }; -} diff --git a/submodules/AsyncDisplayKit/Source/_ASTransitionContext.mm b/submodules/AsyncDisplayKit/Source/_ASTransitionContext.mm deleted file mode 100644 index 40a3573c15..0000000000 --- a/submodules/AsyncDisplayKit/Source/_ASTransitionContext.mm +++ /dev/null @@ -1,104 +0,0 @@ -// -// _ASTransitionContext.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import -#import - - -NSString * const ASTransitionContextFromLayoutKey = @"org.asyncdisplaykit.ASTransitionContextFromLayoutKey"; -NSString * const ASTransitionContextToLayoutKey = @"org.asyncdisplaykit.ASTransitionContextToLayoutKey"; - -@interface _ASTransitionContext () - -@property (weak, nonatomic) id<_ASTransitionContextLayoutDelegate> layoutDelegate; -@property (weak, nonatomic) id<_ASTransitionContextCompletionDelegate> completionDelegate; - -@end - -@implementation _ASTransitionContext - -- (instancetype)initWithAnimation:(BOOL)animated - layoutDelegate:(id<_ASTransitionContextLayoutDelegate>)layoutDelegate - completionDelegate:(id<_ASTransitionContextCompletionDelegate>)completionDelegate -{ - self = [super init]; - if (self) { - _animated = animated; - _layoutDelegate = layoutDelegate; - _completionDelegate = completionDelegate; - } - return self; -} - -#pragma mark - ASContextTransitioning Protocol Implementation - -- (ASLayout *)layoutForKey:(NSString *)key -{ - return [_layoutDelegate transitionContext:self layoutForKey:key]; -} - -- (ASSizeRange)constrainedSizeForKey:(NSString *)key -{ - return [_layoutDelegate transitionContext:self constrainedSizeForKey:key]; -} - -- (CGRect)initialFrameForNode:(ASDisplayNode *)node -{ - return [[self layoutForKey:ASTransitionContextFromLayoutKey] frameForElement:node]; -} - -- (CGRect)finalFrameForNode:(ASDisplayNode *)node -{ - return [[self layoutForKey:ASTransitionContextToLayoutKey] frameForElement:node]; -} - -- (NSArray *)subnodesForKey:(NSString *)key -{ - NSMutableArray *subnodes = [[NSMutableArray alloc] init]; - for (ASLayout *sublayout in [self layoutForKey:key].sublayouts) { - [subnodes addObject:(ASDisplayNode *)sublayout.layoutElement]; - } - return subnodes; -} - -- (NSArray *)insertedSubnodes -{ - return [_layoutDelegate insertedSubnodesWithTransitionContext:self]; -} - -- (NSArray *)removedSubnodes -{ - return [_layoutDelegate removedSubnodesWithTransitionContext:self]; -} - -- (void)completeTransition:(BOOL)didComplete -{ - [_completionDelegate transitionContext:self didComplete:didComplete]; -} - -@end - - -@interface _ASAnimatedTransitionContext () -@property (nonatomic) ASDisplayNode *node; -@property (nonatomic) CGFloat alpha; -@end - -@implementation _ASAnimatedTransitionContext - -+ (instancetype)contextForNode:(ASDisplayNode *)node alpha:(CGFloat)alpha NS_RETURNS_RETAINED -{ - _ASAnimatedTransitionContext *context = [[_ASAnimatedTransitionContext alloc] init]; - context.node = node; - context.alpha = alpha; - return context; -} - -@end diff --git a/submodules/Charts/Sources/Chart Screen/ChartDetailsView.swift b/submodules/Charts/Sources/Chart Screen/ChartDetailsView.swift index 7232c594c3..ecf685aafb 100644 --- a/submodules/Charts/Sources/Chart Screen/ChartDetailsView.swift +++ b/submodules/Charts/Sources/Chart Screen/ChartDetailsView.swift @@ -38,6 +38,7 @@ struct ChartDetailsViewModel { class ChartDetailsView: UIControl { let titleLabel = UILabel() let arrowView = UIImageView() + let activityIndicator = UIActivityIndicatorView() var prefixViews: [UILabel] = [] var labelsViews: [UILabel] = [] @@ -45,11 +46,7 @@ class ChartDetailsView: UIControl { private var viewModel: ChartDetailsViewModel? private var colorMode: ColorMode = .day - - static func fromNib() -> ChartDetailsView { - return Bundle.main.loadNibNamed("ChartDetailsView", owner: nil, options: nil)?.first as! ChartDetailsView - } - + override init(frame: CGRect) { super.init(frame: frame) diff --git a/submodules/Charts/Sources/Chart Screen/ChartStackSection.swift b/submodules/Charts/Sources/Chart Screen/ChartStackSection.swift index bef1a574f4..333bdf1132 100644 --- a/submodules/Charts/Sources/Chart Screen/ChartStackSection.swift +++ b/submodules/Charts/Sources/Chart Screen/ChartStackSection.swift @@ -19,7 +19,6 @@ class ChartStackSection: UIView, ColorModeContainer { var sectionContainerView: UIView var separators: [UIView] = [] - var headerLabel: UILabel! var titleLabel: UILabel! var backButton: UIButton! @@ -30,7 +29,6 @@ class ChartStackSection: UIView, ColorModeContainer { chartView = ChartView() rangeView = RangeChartView() visibilityView = ChartVisibilityView() - headerLabel = UILabel() titleLabel = UILabel() backButton = UIButton() @@ -40,12 +38,19 @@ class ChartStackSection: UIView, ColorModeContainer { sectionContainerView.addSubview(chartView) sectionContainerView.addSubview(rangeView) sectionContainerView.addSubview(visibilityView) + sectionContainerView.addSubview(titleLabel) + sectionContainerView.addSubview(backButton) - headerLabel.font = UIFont.systemFont(ofSize: 14, weight: .regular) titleLabel.font = UIFont.systemFont(ofSize: 14, weight: .bold) + titleLabel.textAlignment = .center visibilityView.clipsToBounds = true backButton.isExclusiveTouch = true - + + backButton.addTarget(self, action: #selector(self.didTapBackButton), for: .touchUpInside) + backButton.setTitle("Zoom Out", for: .normal) + backButton.titleLabel?.font = UIFont.systemFont(ofSize: 14, weight: .regular) + backButton.setTitleColor(UIColor(rgb: 0x007ee5), for: .normal) + backButton.setVisible(false, animated: false) } @@ -56,7 +61,6 @@ class ChartStackSection: UIView, ColorModeContainer { override func awakeFromNib() { super.awakeFromNib() - headerLabel.font = UIFont.systemFont(ofSize: 14, weight: .regular) titleLabel.font = UIFont.systemFont(ofSize: 14, weight: .bold) visibilityView.clipsToBounds = true backButton.isExclusiveTouch = true @@ -95,10 +99,9 @@ class ChartStackSection: UIView, ColorModeContainer { } self.titleLabel.setTextColor(colorMode.chartTitleColor, animated: animated && titleLabel.isVisibleInWindow) - self.headerLabel.setTextColor(colorMode.sectionTitleColor, animated: animated && headerLabel.isVisibleInWindow) } - @IBAction func didTapBackButton() { + @objc private func didTapBackButton() { controller.didTapZoomOut() } @@ -130,15 +133,16 @@ class ChartStackSection: UIView, ColorModeContainer { super.layoutSubviews() let bounds = self.bounds - self.sectionContainerView.frame = CGRect(origin: CGPoint(), size: CGSize(width: bounds.width, height: 350.0)) + self.titleLabel.frame = CGRect(origin: CGPoint(x: backButton.alpha > 0.0 ? 36.0 : 0.0, y: 5.0), size: CGSize(width: bounds.width, height: 28.0)) + self.sectionContainerView.frame = CGRect(origin: CGPoint(), size: CGSize(width: bounds.width, height: 400.0)) self.chartView.frame = CGRect(origin: CGPoint(), size: CGSize(width: bounds.width, height: 250.0)) - self.rangeView.frame = CGRect(origin: CGPoint(x: 0.0, y: 250.0), size: CGSize(width: bounds.width, height: 48.0)) - self.visibilityView.frame = CGRect(origin: CGPoint(x: 0.0, y: 308.0), size: CGSize(width: bounds.width, height: 122.0)) + self.rangeView.frame = CGRect(origin: CGPoint(x: 0.0, y: 250.0), size: CGSize(width: bounds.width, height: 42.0)) + self.visibilityView.frame = CGRect(origin: CGPoint(x: 0.0, y: 308.0), size: CGSize(width: bounds.width, height: 222.0)) + self.backButton.frame = CGRect(x: 0.0, y: 0.0, width: 96.0, height: 38.0) } func setup(controller: BaseChartController, title: String) { self.controller = controller - self.headerLabel.text = title // Chart chartView.renderers = controller.mainChartRenderers @@ -167,6 +171,7 @@ class ChartStackSection: UIView, ColorModeContainer { self.titleLabel.setText(title, animated: animated) } controller.setBackButtonVisibilityClosure = { [unowned self] visible, animated in + self.setNeedsLayout() self.setBackButtonVisible(visible, animated: animated) } controller.refreshChartToolsClosure = { [unowned self] animated in @@ -184,7 +189,7 @@ class ChartStackSection: UIView, ColorModeContainer { controller.chartRangeUpdatedClosure = { [unowned self] (range, animated) in self.rangeView.setRange(range, animated: animated) } - controller.chartRangePagingClosure = { [unowned self] (isEnabled, pageSize) in + controller.chartRangePagingClosure = { [unowned self] (isEnabled, pageSize) in self.rangeView.setRangePaging(enabled: isEnabled, minimumSize: pageSize) } @@ -195,5 +200,10 @@ class ChartStackSection: UIView, ColorModeContainer { controller.initializeChart() updateToolViews(animated: false) + + rangeView.setRange(0.8...1.0, animated: false) + TimeInterval.animationDurationMultipler = 0.00001 + controller.updateChartRange(0.8...1.0, animated: false) + TimeInterval.animationDurationMultipler = 1.0 } } diff --git a/submodules/Charts/Sources/Chart Screen/ChartView.swift b/submodules/Charts/Sources/Chart Screen/ChartView.swift index 39b93d9fa0..37759176a5 100644 --- a/submodules/Charts/Sources/Chart Screen/ChartView.swift +++ b/submodules/Charts/Sources/Chart Screen/ChartView.swift @@ -13,7 +13,7 @@ public protocol ChartViewRenderer: class { func render(context: CGContext, bounds: CGRect, chartFrame: CGRect) } -class ChartView: UIView { +class ChartView: UIControl { override init(frame: CGRect) { super.init(frame: frame) @@ -63,11 +63,18 @@ class ChartView: UIView { var userDidSelectCoordinateClosure: ((CGPoint) -> Void)? var userDidDeselectCoordinateClosure: (() -> Void)? + private var _isTracking: Bool = false + private var touchInitialLocation: CGPoint? + override var isTracking: Bool { + return self._isTracking + } + override func touchesBegan(_ touches: Set, with event: UIEvent?) { if let point = touches.first?.location(in: self) { let fractionPoint = CGPoint(x: (point.x - chartFrame.origin.x) / chartFrame.width, y: (point.y - chartFrame.origin.y) / chartFrame.height) userDidSelectCoordinateClosure?(fractionPoint) + self.touchInitialLocation = point } } @@ -76,15 +83,23 @@ class ChartView: UIView { let fractionPoint = CGPoint(x: (point.x - chartFrame.origin.x) / chartFrame.width, y: (point.y - chartFrame.origin.y) / chartFrame.height) userDidSelectCoordinateClosure?(fractionPoint) + + if let initialPosition = self.touchInitialLocation, abs(initialPosition.x - point.x) > 3.0 { + self._isTracking = true + } } } override func touchesEnded(_ touches: Set, with event: UIEvent?) { userDidDeselectCoordinateClosure?() + self.touchInitialLocation = nil + self._isTracking = false } override func touchesCancelled(_ touches: Set, with event: UIEvent?) { userDidDeselectCoordinateClosure?() + self.touchInitialLocation = nil + self._isTracking = false } // MARK: Details View diff --git a/submodules/Charts/Sources/Chart Screen/ChartsStackViewController.swift b/submodules/Charts/Sources/Chart Screen/ChartsStackViewController.swift deleted file mode 100644 index e8c22e6453..0000000000 --- a/submodules/Charts/Sources/Chart Screen/ChartsStackViewController.swift +++ /dev/null @@ -1,222 +0,0 @@ -// -// ChartsStackViewController.swift -// GraphTest -// -// Created by Andrei Salavei on 4/13/19. -// Copyright © 2019 Andrei Salavei. All rights reserved. -// - -import UIKit - -class ChartsStackViewController: UIViewController { - @IBOutlet private var stackView: UIStackView! - @IBOutlet private var scrollView: UIScrollView! - @IBOutlet private var psLabel: UILabel! - @IBOutlet private var ppsLabel: UILabel! - @IBOutlet private var animationButton: ChartVisibilityItemView! - - private var sections: [ChartStackSection] = [] - - private var colorMode: ColorMode = .night - private var colorModeButton: UIBarButtonItem! - private var performFastAnimation: Bool = false - - override func viewDidLoad() { - super.viewDidLoad() - - title = "Statistics" - colorModeButton = UIBarButtonItem(title: colorMode.switchTitle, style: .plain, target: self, action: #selector(didTapSwitchColorMode)) - navigationItem.rightBarButtonItem = colorModeButton - - apply(colorMode: colorMode, animated: false) - - self.navigationController?.navigationBar.barStyle = .black - self.navigationController?.navigationBar.isTranslucent = false - - self.view.isUserInteractionEnabled = false - animationButton.backgroundColor = .clear - animationButton.tapClosure = { [weak self] in - guard let self = self else { return } - self.setSlowAnimationEnabled(!self.animationButton.isChecked) - } - self.setSlowAnimationEnabled(false) - - loadChart1() - } - - override func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - - DispatchQueue.main.async { - self.view.setNeedsUpdateConstraints() - self.view.setNeedsLayout() - } - } - - func loadChart1() { - ChartsDataLoader.overviewData(type: .generalLines, sync: true, success: { collection in - let generalLinesChartController = GeneralLinesChartController(chartsCollection: collection) - self.addSection(controller: generalLinesChartController, title: "FOLLOWERS") - generalLinesChartController.getDetailsData = { date, completion in - ChartsDataLoader.detaildData(type: .generalLines, date: date, success: { collection in - completion(collection) - }, failure: { error in - completion(nil) - }) - } - DispatchQueue.main.async { - self.loadChart2() - } - }) - } - - func loadChart2() { - ChartsDataLoader.overviewData(type: .twoAxisLines, success: { collection in - let twoAxisLinesChartController = TwoAxisLinesChartController(chartsCollection: collection) - self.addSection(controller: twoAxisLinesChartController, title: "INTERACTIONS") - twoAxisLinesChartController.getDetailsData = { date, completion in - ChartsDataLoader.detaildData(type: .twoAxisLines, date: date, success: { collection in - completion(collection) - }, failure: { error in - completion(nil) - }) - } - DispatchQueue.main.async { - self.loadChart3() - } - }) - } - - func loadChart3() { - ChartsDataLoader.overviewData(type: .stackedBars, success: { collection in - let stackedBarsChartController = StackedBarsChartController(chartsCollection: collection) - self.addSection(controller: stackedBarsChartController, title: "FRUITS") - stackedBarsChartController.getDetailsData = { date, completion in - ChartsDataLoader.detaildData(type: .stackedBars, date: date, success: { collection in - completion(collection) - }, failure: { error in - completion(nil) - }) - } - DispatchQueue.main.async { - self.loadChart4() - } - }) - } - - func loadChart4() { - ChartsDataLoader.overviewData(type: .dailyBars, success: { collection in - let dailyBarsChartController = DailyBarsChartController(chartsCollection: collection) - self.addSection(controller: dailyBarsChartController, title: "VIEWS") - dailyBarsChartController.getDetailsData = { date, completion in - ChartsDataLoader.detaildData(type: .dailyBars, date: date, success: { collection in - completion(collection) - }, failure: { error in - completion(nil) - }) - } - DispatchQueue.main.async { - self.loadChart5() - } - }) - } - - func loadChart5() { - ChartsDataLoader.overviewData(type: .percentPie, success: { collection in - let percentPieChartController = PercentPieChartController(chartsCollection: collection) - self.addSection(controller: percentPieChartController, title: "MORE FRUITS") - self.finalizeChartsLoading() - }) - } - - func setSlowAnimationEnabled(_ isEnabled: Bool) { - animationButton.setChecked(isChecked: isEnabled, animated: true) - if isEnabled { - TimeInterval.animationDurationMultipler = 5 - } else { - TimeInterval.animationDurationMultipler = 1 - } - } - - func finalizeChartsLoading() { - self.view.isUserInteractionEnabled = true - } - - func addSection(controller: BaseChartController, title: String) { - let section = Bundle.main.loadNibNamed("ChartStackSection", owner: nil, options: nil)?.first as! ChartStackSection - section.frame = UIScreen.main.bounds - section.layoutIfNeeded() - section.setup(controller: controller, title: title) - section.apply(colorMode: colorMode, animated: false) - stackView.addArrangedSubview(section) - sections.append(section) - } - - override var preferredStatusBarStyle: UIStatusBarStyle { - return (colorMode == .day) ? .default : .lightContent - } - - override var preferredStatusBarUpdateAnimation: UIStatusBarAnimation { - return .fade - } - - @objc private func didTapSwitchColorMode() { - self.colorMode = self.colorMode == .day ? .night : .day - apply(colorMode: self.colorMode, animated: !performFastAnimation) - colorModeButton.title = colorMode.switchTitle - } -} - -extension ChartsStackViewController: UIScrollViewDelegate { - func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { - performFastAnimation = decelerate - } - - func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { - performFastAnimation = false - } -} - -extension ChartsStackViewController: ColorModeContainer { - func apply(colorMode: ColorMode, animated: Bool) { - - UIView.perform(animated: animated) { - self.psLabel.setTextColor(colorMode.sectionTitleColor, animated: animated && self.psLabel.isVisibleInWindow) - self.ppsLabel.setTextColor(colorMode.sectionTitleColor, animated: animated && self.ppsLabel.isVisibleInWindow) - self.animationButton.item = ChartVisibilityItem(title: "Enable slow animations", - color: colorMode.sectionTitleColor) - - self.view.backgroundColor = colorMode.tableBackgroundColor - - if (animated) { - let animation = CATransition() - animation.timingFunction = CAMediaTimingFunction.init(name: .linear) - animation.type = .fade - animation.duration = .defaultDuration - self.navigationController?.navigationBar.layer.add(animation, forKey: "kCATransitionColorFade") - } - - self.navigationController?.navigationBar.tintColor = colorMode.actionButtonColor - self.navigationController?.navigationBar.barTintColor = colorMode.chartBackgroundColor - self.navigationController?.navigationBar.titleTextAttributes = [.font: UIFont.systemFont(ofSize: 17, weight: .medium), - .foregroundColor: colorMode.viewTintColor] - self.view.layoutIfNeeded() - } - self.setNeedsStatusBarAppearanceUpdate() - - for section in sections { - section.apply(colorMode: colorMode, animated: animated && section.isVisibleInWindow) - } - } -} - -extension ColorMode { - var switchTitle: String { - switch self { - case .day: - return "Night Mode" - case .night: - return "Day Mode" - } - } -} diff --git a/submodules/Charts/Sources/Chart Screen/RangeChartView.swift b/submodules/Charts/Sources/Chart Screen/RangeChartView.swift index 91089c1de8..bc62636fbd 100644 --- a/submodules/Charts/Sources/Chart Screen/RangeChartView.swift +++ b/submodules/Charts/Sources/Chart Screen/RangeChartView.swift @@ -42,7 +42,8 @@ class RangeChartView: UIControl { private let cropFrameView = UIImageView() private var selectedMarker: Marker? - private var selectedMarkerHorizontalOffet: CGFloat = 0 + private var selectedMarkerHorizontalOffset: CGFloat = 0 + private var selectedMarkerInitialLocation: CGPoint? private var isBoundCropHighlighted: Bool = false private var isRangePagingEnabled: Bool = false @@ -137,18 +138,22 @@ class RangeChartView: UIControl { if abs(locationInView(for: upperBound) - point.x + Constants.markerSelectionRange / 2) < Constants.markerSelectionRange { selectedMarker = .upper - selectedMarkerHorizontalOffet = point.x - locationInView(for: upperBound) + selectedMarkerHorizontalOffset = point.x - locationInView(for: upperBound) + selectedMarkerInitialLocation = point isBoundCropHighlighted = true } else if abs(locationInView(for: lowerBound) - point.x - Constants.markerSelectionRange / 2) < Constants.markerSelectionRange { selectedMarker = .lower - selectedMarkerHorizontalOffet = point.x - locationInView(for: lowerBound) + selectedMarkerHorizontalOffset = point.x - locationInView(for: lowerBound) + selectedMarkerInitialLocation = point isBoundCropHighlighted = true } else if point.x > locationInView(for: lowerBound) && point.x < locationInView(for: upperBound) { selectedMarker = .center - selectedMarkerHorizontalOffet = point.x - locationInView(for: lowerBound) + selectedMarkerHorizontalOffset = point.x - locationInView(for: lowerBound) + selectedMarkerInitialLocation = point isBoundCropHighlighted = true } else { selectedMarker = nil + selectedMarkerInitialLocation = nil return } @@ -160,10 +165,14 @@ class RangeChartView: UIControl { guard let selectedMarker = selectedMarker else { return } guard let point = touches.first?.location(in: self) else { return } - let horizontalPosition = point.x - selectedMarkerHorizontalOffet + let horizontalPosition = point.x - selectedMarkerHorizontalOffset let fraction = fractionFor(offsetX: horizontalPosition) updateMarkerOffset(selectedMarker, fraction: fraction) + if let initialPosition = selectedMarkerInitialLocation, abs(initialPosition.x - point.x) > 3.0 { + self._isTracking = true + } + sendActions(for: .valueChanged) } @@ -175,11 +184,12 @@ class RangeChartView: UIControl { } guard let point = touches.first?.location(in: self) else { return } - let horizontalPosition = point.x - selectedMarkerHorizontalOffet + let horizontalPosition = point.x - selectedMarkerHorizontalOffset let fraction = fractionFor(offsetX: horizontalPosition) updateMarkerOffset(selectedMarker, fraction: fraction) self.selectedMarker = nil + self.selectedMarkerInitialLocation = nil self.isBoundCropHighlighted = false if bounds.contains(point) { sendActions(for: .touchUpInside) @@ -187,13 +197,22 @@ class RangeChartView: UIControl { sendActions(for: .touchUpOutside) } rangeDidChangeClosure?(lowerBound...upperBound) + + self._isTracking = false } override func touchesCancelled(_ touches: Set, with event: UIEvent?) { self.selectedMarker = nil + self.selectedMarkerInitialLocation = nil self.isBoundCropHighlighted = false + self._isTracking = false sendActions(for: .touchCancel) } + + private var _isTracking: Bool = false + override var isTracking: Bool { + return self._isTracking + } } private extension RangeChartView { diff --git a/submodules/Charts/Sources/ChartNode.swift b/submodules/Charts/Sources/ChartNode.swift index b6c6da436c..20e319098a 100644 --- a/submodules/Charts/Sources/ChartNode.swift +++ b/submodules/Charts/Sources/ChartNode.swift @@ -4,6 +4,13 @@ import Display import AsyncDisplayKit import AppBundle +public enum ChartType { + case lines + case twoAxis + case pie + case bars +} + public final class ChartNode: ASDisplayNode { private var chartView: ChartStackSection { return self.view as! ChartStackSection @@ -23,14 +30,32 @@ public final class ChartNode: ASDisplayNode { self.view.disablesInteractiveTransitionGestureRecognizer = true } - public func setup(_ data: String, bar: Bool = false) { + public func setup(_ data: String, type: ChartType, getDetailsData: @escaping (Date, (String?) -> Void) -> Void) { if let data = data.data(using: .utf8) { ChartsDataManager().readChart(data: data, extraCopiesCount: 0, sync: true, success: { [weak self] collection in let controller: BaseChartController - if bar { - controller = DailyBarsChartController(chartsCollection: collection) - } else { - controller = GeneralLinesChartController(chartsCollection: collection) + switch type { + case .lines: + controller = GeneralLinesChartController(chartsCollection: collection) + controller.getDetailsData = { date, completion in + getDetailsData(date, { detailsData in + if let detailsData = detailsData, let data = detailsData.data(using: .utf8) { + ChartsDataManager().readChart(data: data, extraCopiesCount: 0, sync: true, success: { [weak self] collection in + completion(collection) + }) { error in + completion(nil) + } + } else { + completion(nil) + } + }) + } + case .twoAxis: + controller = TwoAxisLinesChartController(chartsCollection: collection) + case .pie: + controller = PercentPieChartController(chartsCollection: collection) + case .bars: + controller = StackedBarsChartController(chartsCollection: collection) } if let strongSelf = self { strongSelf.chartView.setup(controller: controller, title: "") diff --git a/submodules/Charts/Sources/Charts Reader/ChartsCollection.swift b/submodules/Charts/Sources/Charts Reader/ChartsCollection.swift index f3ecd8215d..f0348f7ab6 100644 --- a/submodules/Charts/Sources/Charts Reader/ChartsCollection.swift +++ b/submodules/Charts/Sources/Charts Reader/ChartsCollection.swift @@ -58,7 +58,7 @@ extension ChartsCollection { switch type { case .axix: axixValuesToSetup = try column.dropFirst().map { Date(timeIntervalSince1970: try Convert.doubleFrom($0) / 1000) } - case .chart, .bar, .area: + case .chart, .bar, .area, .step: guard let colorString = colors[columnId], let color = UIColor(hexString: colorString) else { throw ChartsError.generalConversion("Unable to get color name from: \(colors) - \(columnId)") @@ -76,7 +76,7 @@ extension ChartsCollection { guard axixValuesToSetup.isEmpty == false, chartToSetup.isEmpty == false, chartToSetup.firstIndex(where: { $0.values.count != axixValuesToSetup.count }) == nil else { - throw ChartsError.generalConversion("Saniazing: Invalid number of items: \(axixValuesToSetup), \(chartToSetup)") + throw ChartsError.generalConversion("Sanitazing: Invalid number of items: \(axixValuesToSetup), \(chartToSetup)") } self.axisValues = axixValuesToSetup self.chartValues = chartToSetup @@ -88,4 +88,5 @@ private enum ColumnType: String { case chart = "line" case area = "area" case bar = "bar" + case step = "step" } diff --git a/submodules/Charts/Sources/Charts/Controllers/BaseChartController.swift b/submodules/Charts/Sources/Charts/Controllers/BaseChartController.swift index 7f91e50ea5..0fcaef599f 100644 --- a/submodules/Charts/Sources/Charts/Controllers/BaseChartController.swift +++ b/submodules/Charts/Sources/Charts/Controllers/BaseChartController.swift @@ -74,7 +74,7 @@ class BaseChartController: ColorModeContainer { init(chartsCollection: ChartsCollection) { self.initialChartsCollection = chartsCollection } - + var mainChartRenderers: [ChartViewRenderer] { fatalError("Abstract") } @@ -128,7 +128,11 @@ class BaseChartController: ColorModeContainer { fatalError("Abstract") } - func updateChartRange(_ rangeFraction: ClosedRange) { + func updateChartRangeTitle(animated: Bool) { + + } + + func updateChartRange(_ rangeFraction: ClosedRange, animated: Bool = true) { fatalError("Abstract") } diff --git a/submodules/Charts/Sources/Charts/Controllers/Lines/BaseLinesChartController.swift b/submodules/Charts/Sources/Charts/Controllers/Lines/BaseLinesChartController.swift index 60d5069d10..d7b67c4a21 100644 --- a/submodules/Charts/Sources/Charts/Controllers/Lines/BaseLinesChartController.swift +++ b/submodules/Charts/Sources/Charts/Controllers/Lines/BaseLinesChartController.swift @@ -25,9 +25,9 @@ class BaseLinesChartController: BaseChartController { func setupChartCollection(chartsCollection: ChartsCollection, animated: Bool, isZoomed: Bool) { if animated { - TimeInterval.setDefaultSuration(.expandAnimationDuration) + TimeInterval.setDefaultDuration(.expandAnimationDuration) DispatchQueue.main.asyncAfter(deadline: .now() + .expandAnimationDuration) { - TimeInterval.setDefaultSuration(.osXDuration) + TimeInterval.setDefaultDuration(.osXDuration) } } @@ -39,7 +39,7 @@ class BaseLinesChartController: BaseChartController { updateChartRangeTitle(animated: animated) } - func updateChartRangeTitle(animated: Bool) { + override func updateChartRangeTitle(animated: Bool) { let fromDate = Date(timeIntervalSince1970: TimeInterval(zoomedChartRange.lowerBound) + .hour) let toDate = Date(timeIntervalSince1970: TimeInterval(zoomedChartRange.upperBound)) if Calendar.utc.startOfDay(for: fromDate) == Calendar.utc.startOfDay(for: toDate) { @@ -64,7 +64,7 @@ class BaseLinesChartController: BaseChartController { isChartInteractionBegun = false } - override func updateChartRange(_ rangeFraction: ClosedRange) { + override func updateChartRange(_ rangeFraction: ClosedRange, animated: Bool) { } diff --git a/submodules/Charts/Sources/Charts/Controllers/Lines/GeneralLinesChartController.swift b/submodules/Charts/Sources/Charts/Controllers/Lines/GeneralLinesChartController.swift index 3d52888d72..e122468207 100644 --- a/submodules/Charts/Sources/Charts/Controllers/Lines/GeneralLinesChartController.swift +++ b/submodules/Charts/Sources/Charts/Controllers/Lines/GeneralLinesChartController.swift @@ -172,7 +172,7 @@ class GeneralLinesChartController: BaseLinesChartController { return visibleCharts } - override func updateChartRange(_ rangeFraction: ClosedRange) { + override func updateChartRange(_ rangeFraction: ClosedRange, animated: Bool) { cancelChartInteraction() let horizontalRange = ClosedRange(uncheckedBounds: @@ -183,8 +183,8 @@ class GeneralLinesChartController: BaseLinesChartController { updateChartRangeTitle(animated: true) updateMainChartHorizontalRange(range: horizontalRange, animated: false) - updateHorizontalLimists(horizontalRange: horizontalRange, animated: true) - updateVerticalLimitsAndRange(horizontalRange: horizontalRange, animated: true) + updateHorizontalLimists(horizontalRange: horizontalRange, animated: animated) + updateVerticalLimitsAndRange(horizontalRange: horizontalRange, animated: animated) } func updateMainChartHorizontalRange(range: ClosedRange, animated: Bool) { diff --git a/submodules/Charts/Sources/Charts/Controllers/Lines/TwoAxisLinesChartController.swift b/submodules/Charts/Sources/Charts/Controllers/Lines/TwoAxisLinesChartController.swift index 251e76271e..a07be6a703 100644 --- a/submodules/Charts/Sources/Charts/Controllers/Lines/TwoAxisLinesChartController.swift +++ b/submodules/Charts/Sources/Charts/Controllers/Lines/TwoAxisLinesChartController.swift @@ -194,7 +194,7 @@ class TwoAxisLinesChartController: BaseLinesChartController { self.setupChartCollection(chartsCollection: initialChartCollection, animated: true, isZoomed: false) } - override func updateChartRange(_ rangeFraction: ClosedRange) { + override func updateChartRange(_ rangeFraction: ClosedRange, animated: Bool) { cancelChartInteraction() let horizontalRange = ClosedRange(uncheckedBounds: @@ -205,8 +205,8 @@ class TwoAxisLinesChartController: BaseLinesChartController { updateChartRangeTitle(animated: true) updateMainChartHorizontalRange(range: horizontalRange, animated: false) - updateHorizontalLimists(horizontalRange: horizontalRange, animated: true) - updateVerticalLimitsAndRange(horizontalRange: horizontalRange, animated: true) + updateHorizontalLimists(horizontalRange: horizontalRange, animated: animated) + updateVerticalLimitsAndRange(horizontalRange: horizontalRange, animated: animated) } func updateMainChartHorizontalRange(range: ClosedRange, animated: Bool) { diff --git a/submodules/Charts/Sources/Charts/Controllers/Percent And Pie/PercentPieChartController.swift b/submodules/Charts/Sources/Charts/Controllers/Percent And Pie/PercentPieChartController.swift index 484d8a2f11..b5df648b11 100644 --- a/submodules/Charts/Sources/Charts/Controllers/Percent And Pie/PercentPieChartController.swift +++ b/submodules/Charts/Sources/Charts/Controllers/Percent And Pie/PercentPieChartController.swift @@ -93,9 +93,9 @@ class PercentPieChartController: BaseChartController { func switchToChart(chartsCollection: ChartsCollection, isZoomed: Bool, animated: Bool) { if animated { - TimeInterval.setDefaultSuration(.expandAnimationDuration) + TimeInterval.setDefaultDuration(.expandAnimationDuration) DispatchQueue.main.asyncAfter(deadline: .now() + .expandAnimationDuration) { - TimeInterval.setDefaultSuration(.osXDuration) + TimeInterval.setDefaultDuration(.osXDuration) } } @@ -263,7 +263,7 @@ class PercentPieChartController: BaseChartController { }) } - override func updateChartRange(_ rangeFraction: ClosedRange) { + override func updateChartRange(_ rangeFraction: ClosedRange, animated: Bool) { if isZoomed { return pieController.chartRangeFractionDidUpdated(rangeFraction) } else { diff --git a/submodules/Charts/Sources/Charts/Controllers/Stacked Bars/DailyBarsChartController.swift b/submodules/Charts/Sources/Charts/Controllers/Stacked Bars/DailyBarsChartController.swift index ae83803bb1..008313d1cf 100644 --- a/submodules/Charts/Sources/Charts/Controllers/Stacked Bars/DailyBarsChartController.swift +++ b/submodules/Charts/Sources/Charts/Controllers/Stacked Bars/DailyBarsChartController.swift @@ -84,9 +84,9 @@ class DailyBarsChartController: BaseChartController { func switchToChart(chartsCollection: ChartsCollection, isZoomed: Bool, animated: Bool) { if animated { - TimeInterval.setDefaultSuration(.expandAnimationDuration) + TimeInterval.setDefaultDuration(.expandAnimationDuration) DispatchQueue.main.asyncAfter(deadline: .now() + .expandAnimationDuration) { - TimeInterval.setDefaultSuration(.osXDuration) + TimeInterval.setDefaultDuration(.osXDuration) } } @@ -225,7 +225,7 @@ class DailyBarsChartController: BaseChartController { switchToChart(chartsCollection: barsController.chartsCollection, isZoomed: false, animated: true) } - override func updateChartRange(_ rangeFraction: ClosedRange) { + override func updateChartRange(_ rangeFraction: ClosedRange, animated: Bool) { if isZoomed { return linesController.chartRangeFractionDidUpdated(rangeFraction) } else { diff --git a/submodules/Charts/Sources/Charts/Controllers/Stacked Bars/StackedBarsChartController.swift b/submodules/Charts/Sources/Charts/Controllers/Stacked Bars/StackedBarsChartController.swift index ab836d00d2..4dd0fa9d91 100644 --- a/submodules/Charts/Sources/Charts/Controllers/Stacked Bars/StackedBarsChartController.swift +++ b/submodules/Charts/Sources/Charts/Controllers/Stacked Bars/StackedBarsChartController.swift @@ -79,9 +79,9 @@ class StackedBarsChartController: BaseChartController { func switchToChart(chartsCollection: ChartsCollection, isZoomed: Bool, animated: Bool) { if animated { - TimeInterval.setDefaultSuration(.expandAnimationDuration) + TimeInterval.setDefaultDuration(.expandAnimationDuration) DispatchQueue.main.asyncAfter(deadline: .now() + .expandAnimationDuration) { - TimeInterval.setDefaultSuration(.osXDuration) + TimeInterval.setDefaultDuration(.osXDuration) } } @@ -226,7 +226,7 @@ class StackedBarsChartController: BaseChartController { switchToChart(chartsCollection: barsController.chartsCollection, isZoomed: false, animated: true) } - override func updateChartRange(_ rangeFraction: ClosedRange) { + override func updateChartRange(_ rangeFraction: ClosedRange, animated: Bool) { if isZoomed { return zoomedBarsController.chartRangeFractionDidUpdated(rangeFraction) } else { diff --git a/submodules/Charts/Sources/Helpers/TimeInterval+Utils.swift b/submodules/Charts/Sources/Helpers/TimeInterval+Utils.swift index 204b1e861a..d093d2fc0c 100644 --- a/submodules/Charts/Sources/Helpers/TimeInterval+Utils.swift +++ b/submodules/Charts/Sources/Helpers/TimeInterval+Utils.swift @@ -21,7 +21,7 @@ extension TimeInterval { } private static var innerDefaultDuration: TimeInterval = osXDuration - static func setDefaultSuration(_ duration: TimeInterval) { + static func setDefaultDuration(_ duration: TimeInterval) { innerDefaultDuration = duration } } diff --git a/submodules/ChatListUI/BUCK b/submodules/ChatListUI/BUCK index 77299792c5..5dc03a48f1 100644 --- a/submodules/ChatListUI/BUCK +++ b/submodules/ChatListUI/BUCK @@ -43,6 +43,8 @@ static_library( "//submodules/PhoneNumberFormat:PhoneNumberFormat", "//submodules/TelegramIntents:TelegramIntents", "//submodules/ItemListPeerActionItem:ItemListPeerActionItem", + "//submodules/AnimatedStickerNode:AnimatedStickerNode", + "//submodules/SolidRoundedButtonNode:SolidRoundedButtonNode", ], frameworks = [ "$SDKROOT/System/Library/Frameworks/Foundation.framework", diff --git a/submodules/ChatListUI/Sources/ChatContextMenus.swift b/submodules/ChatListUI/Sources/ChatContextMenus.swift index 5d19de7a98..28ca57540d 100644 --- a/submodules/ChatListUI/Sources/ChatContextMenus.swift +++ b/submodules/ChatListUI/Sources/ChatContextMenus.swift @@ -17,10 +17,10 @@ func archiveContextMenuItems(context: AccountContext, groupId: PeerGroupId, chat return context.account.postbox.transaction { [weak chatListController] transaction -> [ContextMenuItem] in var items: [ContextMenuItem] = [] - if !transaction.getUnreadChatListPeerIds(groupId: groupId).isEmpty { + if !transaction.getUnreadChatListPeerIds(groupId: groupId, filterPredicate: nil).isEmpty { items.append(.action(ContextMenuActionItem(text: strings.ChatList_Context_MarkAllAsRead, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/MarkAsRead"), color: theme.contextMenu.primaryColor) }, action: { [weak chatListController] _, f in let _ = (context.account.postbox.transaction { transaction in - markAllChatsAsReadInteractively(transaction: transaction, viewTracker: context.account.viewTracker, groupId: groupId) + markAllChatsAsReadInteractively(transaction: transaction, viewTracker: context.account.viewTracker, groupId: groupId, filterPredicate: nil) } |> deliverOnMainQueue).start(completed: { f(.default) diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index 5cfbbe5a93..3b74034e53 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -96,7 +96,7 @@ private final class ContextControllerContentSourceImpl: ContextControllerContent } } -public class ChatListControllerImpl: TelegramBaseController, ChatListController, UIViewControllerPreviewingDelegate, TabBarContainedController { +public class ChatListControllerImpl: TelegramBaseController, ChatListController, UIViewControllerPreviewingDelegate { private var validLayout: ContainerViewLayout? public let context: AccountContext @@ -167,6 +167,8 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController, super.init(context: context, navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData), mediaAccessoryPanelVisibility: .always, locationBroadcastPanelSource: .summary) + self.hasTabBarItemContextAction = true + self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style let title: String @@ -231,10 +233,22 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController, if strongSelf.chatListDisplayNode.searchDisplayController != nil { strongSelf.deactivateSearch(animated: true) } else { - if let searchContentNode = strongSelf.searchContentNode { - searchContentNode.updateExpansionProgress(1.0, animated: true) + switch strongSelf.chatListDisplayNode.chatListNode.visibleContentOffset() { + case .none, .unknown: + if let searchContentNode = strongSelf.searchContentNode { + searchContentNode.updateExpansionProgress(1.0, animated: true) + } + strongSelf.chatListDisplayNode.chatListNode.scrollToPosition(.top) + case let .known(offset): + if offset <= navigationBarSearchContentHeight + 1.0 { + strongSelf.tabContainerNode.tabSelected?(.all) + } else { + if let searchContentNode = strongSelf.searchContentNode { + searchContentNode.updateExpansionProgress(1.0, animated: true) + } + strongSelf.chatListDisplayNode.chatListNode.scrollToPosition(.top) + } } - strongSelf.chatListDisplayNode.chatListNode.scrollToPosition(.top) } } self.longTapWithTabBar = { [weak self] in @@ -411,9 +425,9 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController, ApplicationSpecificPreferencesKeys.chatListFilterSettings ])) let filterItems = chatListFilterItems(context: context) - |> map { items -> [ChatListFilterTabEntry] in + |> map { totalCount, items -> [ChatListFilterTabEntry] in var result: [ChatListFilterTabEntry] = [] - result.append(.all) + result.append(.all(unreadCount: totalCount)) for (filter, unreadCount) in items { result.append(.filter(id: filter.id, text: filter.title ?? "", unreadCount: unreadCount)) } @@ -439,7 +453,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController, } var resolvedItems = filterItems - if !filterSettings.displayTabs { + if !filterSettings.displayTabs || groupId != .root { resolvedItems = [] } @@ -466,7 +480,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController, strongSelf.containerLayoutUpdated(layout, transition: .immediate) (strongSelf.parent as? TabBarController)?.updateLayout() } else { - strongSelf.tabContainerNode.update(size: CGSize(width: layout.size.width, height: 46.0), sideInset: layout.safeInsets.left, filters: resolvedItems, selectedFilter: selectedEntryId, presentationData: strongSelf.presentationData, transition: .animated(duration: 0.4, curve: .spring)) + strongSelf.tabContainerNode.update(size: CGSize(width: layout.size.width, height: NavigationBar.defaultSecondaryContentHeight), sideInset: layout.safeInsets.left, filters: resolvedItems, selectedFilter: selectedEntryId, presentationData: strongSelf.presentationData, transition: .animated(duration: 0.4, curve: .spring)) } } }) @@ -484,28 +498,120 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController, guard let strongSelf = self else { return } + let previousFilter = strongSelf.chatListDisplayNode.chatListNode.chatListFilter + let updatedFilter: ChatListFilter? switch id { case .all: - strongSelf.chatListDisplayNode.chatListNode.chatListFilter = nil + updatedFilter = nil case let .filter(id): var found = false + var foundValue: ChatListFilter? for filter in filters { if filter.id == id { - strongSelf.chatListDisplayNode.chatListNode.chatListFilter = filter + foundValue = filter found = true break } } - if !found { - strongSelf.chatListDisplayNode.chatListNode.chatListFilter = nil + if found { + updatedFilter = foundValue + } else { + updatedFilter = nil } } + if previousFilter?.id != updatedFilter?.id { + var paneSwitchAnimationDirection: ChatListNodePaneSwitchAnimationDirection? + if let previousId = previousFilter?.id, let updatedId = updatedFilter?.id, let previousIndex = filters.index(where: { $0.id == previousId }), let updatedIndex = filters.index(where: { $0.id == updatedId }) { + if previousIndex > updatedIndex { + paneSwitchAnimationDirection = .right + } else { + paneSwitchAnimationDirection = .left + } + } else if (previousFilter != nil) != (updatedFilter != nil) { + if previousFilter != nil { + paneSwitchAnimationDirection = .right + } else { + paneSwitchAnimationDirection = .left + } + } + if let direction = paneSwitchAnimationDirection { + strongSelf.chatListDisplayNode.chatListNode.paneSwitchAnimation = (direction, .animated(duration: 0.4, curve: .spring)) + } + } + strongSelf.chatListDisplayNode.chatListNode.updateFilter(updatedFilter) }) } self.tabContainerNode.addFilter = { [weak self] in self?.openFilterSettings() } + + self.tabContainerNode.contextGesture = { [weak self] id, sourceNode, gesture in + guard let strongSelf = self else { + return + } + var items: [ContextMenuItem] = [] + items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.Common_Edit, icon: { _ in + return nil + }, action: { c, f in + c.dismiss(completion: { + guard let strongSelf = self else { + return + } + let _ = (strongSelf.context.account.postbox.transaction { transaction -> [ChatListFilter] in + let settings = transaction.getPreferencesEntry(key: PreferencesKeys.chatListFilters) as? ChatListFiltersState ?? ChatListFiltersState.default + return settings.filters + } + |> deliverOnMainQueue).start(next: { presetList in + guard let strongSelf = self else { + return + } + var found = false + for filter in presetList { + if filter.id == id { + strongSelf.push(chatListFilterPresetController(context: strongSelf.context, currentPreset: filter, updated: { _ in })) + f(.dismissWithoutContent) + found = true + break + } + } + }) + }) + }))) + if let chatListFilter = strongSelf.chatListDisplayNode.chatListNode.chatListFilter, chatListFilter.includePeers.count < 100 { + //TODO:localization + items.append(.action(ContextMenuActionItem(text: "Add Chats", icon: { _ in + return nil + }, action: { c, f in + c.dismiss(completion: { + guard let strongSelf = self else { + return + } + let _ = (strongSelf.context.account.postbox.transaction { transaction -> [ChatListFilter] in + let settings = transaction.getPreferencesEntry(key: PreferencesKeys.chatListFilters) as? ChatListFiltersState ?? ChatListFiltersState.default + return settings.filters + } + |> deliverOnMainQueue).start(next: { presetList in + guard let strongSelf = self else { + return + } + var found = false + for filter in presetList { + if filter.id == id { + strongSelf.push(chatListFilterAddChatsController(context: strongSelf.context, filter: filter)) + f(.dismissWithoutContent) + found = true + break + } + } + }) + }) + }))) + } + + let controller = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .extracted(ChatListHeaderBarContextExtractedContentSource(controller: strongSelf, sourceNode: sourceNode)), items: .single(items), reactionItems: [], recognizer: nil, gesture: gesture) + strongSelf.context.sharedContext.mainWindow?.presentInGlobalOverlay(controller) + } } required public init(coder aDecoder: NSCoder) { @@ -571,7 +677,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController, override public func loadDisplayNode() { self.displayNode = ChatListControllerNode(context: self.context, groupId: self.groupId, filter: self.filter, previewing: self.previewing, controlsHistoryPreload: self.controlsHistoryPreload, presentationData: self.presentationData, controller: self) - self.chatListFilterValue.set(self.chatListDisplayNode.chatListNode.chatListFilterSignal) + self.chatListFilterValue.set(self.chatListDisplayNode.chatListNode.appliedChatListFilterSignal) self.chatListDisplayNode.navigationBar = self.navigationBar @@ -589,6 +695,12 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController, } } + self.chatListDisplayNode.chatListNode.present = { [weak self] c in + if let strongSelf = self { + self?.present(c, in: .window(.root)) + } + } + self.chatListDisplayNode.chatListNode.toggleArchivedFolderHiddenByDefault = { [weak self] in guard let strongSelf = self else { return @@ -777,11 +889,22 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController, self.chatListDisplayNode.isEmptyUpdated = { [weak self] isEmpty in if let strongSelf = self, let searchContentNode = strongSelf.searchContentNode, let _ = strongSelf.validLayout { if isEmpty { - searchContentNode.updateListVisibleContentOffset(.known(0.0)) + //searchContentNode.updateListVisibleContentOffset(.known(0.0)) } } } + self.chatListDisplayNode.emptyListAction = { [weak self] in + guard let strongSelf = self else { + return + } + if let filter = strongSelf.chatListDisplayNode.chatListNode.chatListFilter { + strongSelf.push(chatListFilterPresetController(context: strongSelf.context, currentPreset: filter, updated: { _ in })) + } else { + strongSelf.composePressed() + } + } + self.chatListDisplayNode.toolbarActionSelected = { [weak self] action in self?.toolbarActionSelected(action: action) } @@ -866,7 +989,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController, } } } - toolbar = Toolbar(leftAction: leftAction, rightAction: ToolbarAction(title: presentationData.strings.Common_Delete, isEnabled: options.delete), middleAction: ToolbarAction(title: presentationData.strings.ChatList_ArchiveAction, isEnabled: archiveEnabled)) + toolbar = Toolbar(leftAction: leftAction, rightAction: ToolbarAction(title: presentationData.strings.Common_Delete, isEnabled: options.delete), middleAction: strongSelf.chatListDisplayNode.chatListNode.chatListFilter != nil ? nil : ToolbarAction(title: presentationData.strings.ChatList_ArchiveAction, isEnabled: archiveEnabled)) } } else { if let (options, peerIds) = peerIdsAndOptions { @@ -1039,8 +1162,8 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController, tabContainerOffset += 44.0 + 44.0 + 44.0 } - transition.updateFrame(node: self.tabContainerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: self.visualNavigationInsetHeight - 46.0 + tabContainerOffset), size: CGSize(width: layout.size.width, height: 46.0))) - self.tabContainerNode.update(size: CGSize(width: layout.size.width, height: 46.0), sideInset: layout.safeInsets.left, filters: self.tabContainerData?.0 ?? [], selectedFilter: self.tabContainerData?.1, presentationData: self.presentationData, transition: .animated(duration: 0.4, curve: .spring)) + transition.updateFrame(node: self.tabContainerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: self.visualNavigationInsetHeight - self.additionalHeight - NavigationBar.defaultSecondaryContentHeight + tabContainerOffset), size: CGSize(width: layout.size.width, height: NavigationBar.defaultSecondaryContentHeight))) + self.tabContainerNode.update(size: CGSize(width: layout.size.width, height: NavigationBar.defaultSecondaryContentHeight), sideInset: layout.safeInsets.left, filters: self.tabContainerData?.0 ?? [], selectedFilter: self.tabContainerData?.1, presentationData: self.presentationData, transition: .animated(duration: 0.4, curve: .spring)) if let searchContentNode = self.searchContentNode, layout.inVoiceOver != wasInVoiceOver { searchContentNode.updateListVisibleContentOffset(.known(0.0)) @@ -1311,7 +1434,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController, } else { let groupId = self.groupId signal = self.context.account.postbox.transaction { transaction -> Void in - markAllChatsAsReadInteractively(transaction: transaction, viewTracker: context.account.viewTracker, groupId: groupId) + markAllChatsAsReadInteractively(transaction: transaction, viewTracker: context.account.viewTracker, groupId: groupId, filterPredicate: self.chatListDisplayNode.chatListNode.chatListFilter.flatMap(chatListFilterPredicate)) } } let _ = (signal @@ -1946,36 +2069,179 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController, guard let strongSelf = self else { return } - strongSelf.push(chatListFilterPresetListController(context: strongSelf.context, updated: { presets in - guard let strongSelf = self else { - return - } - /*if let currentPreset = strongSelf.chatListDisplayNode.chatListNode.chatListFilter { - var found = false - if let index = presets.index(where: { $0.id == currentPreset.id }) { - found = true - if currentPreset != presets[index] { - strongSelf.chatListDisplayNode.chatListNode.chatListFilter = presets[index] - } - } - if !found { - strongSelf.chatListDisplayNode.chatListNode.chatListFilter = nil - } - }*/ + strongSelf.push(chatListFilterPresetListController(context: strongSelf.context, updated: { _ in })) }, updatePreset: { value in guard let strongSelf = self else { return } - strongSelf.push(ChatListControllerImpl(context: strongSelf.context, groupId: .root, filter: value, controlsHistoryPreload: false, hideNetworkActivityStatus: true, previewing: false, enableDebugActions: false)) - //strongSelf.chatListDisplayNode.chatListNode.chatListFilter = value + if let value = value { + strongSelf.tabContainerNode.tabSelected?(.filter(value.id)) + } }) strongSelf.context.sharedContext.mainWindow?.present(controller, on: .root) }) } } - public func updateTabBarPreviewingControllerPresentation(_ update: TabBarContainedControllerPresentationUpdate) { - + override public func tabBarItemContextAction(sourceNode: ContextExtractedContentContainingNode, gesture: ContextGesture) { + let _ = (combineLatest(queue: .mainQueue(), + self.context.account.postbox.transaction { transaction -> [ChatListFilter] in + let settings = transaction.getPreferencesEntry(key: PreferencesKeys.chatListFilters) as? ChatListFiltersState ?? ChatListFiltersState.default + return settings.filters + }, + chatListFilterItems(context: self.context) + |> take(1) + ) + |> deliverOnMainQueue).start(next: { [weak self] presetList, filterItemsAndTotalCount in + guard let strongSelf = self else { + return + } + + let (_, filterItems) = filterItemsAndTotalCount + + var items: [ContextMenuItem] = [] + items.append(.action(ContextMenuActionItem(text: presetList.isEmpty ? "Add Filter" : "Edit Filters", icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Add"), color: theme.contextMenu.primaryColor) + }, action: { c, f in + c.dismiss(completion: { + guard let strongSelf = self else { + return + } + if presetList.isEmpty { + if let navigationController = strongSelf.navigationController as? NavigationController { + var viewControllers = navigationController.viewControllers + //viewControllers.append(chatListFilterPresetListController(context: strongSelf.context, updated: { _ in })) + viewControllers.append(chatListFilterPresetController(context: strongSelf.context, currentPreset: nil, updated: { _ in })) + navigationController.setViewControllers(viewControllers, animated: true) + } + } else { + strongSelf.push(chatListFilterPresetListController(context: strongSelf.context, updated: { _ in })) + } + }) + }))) + + if !presetList.isEmpty { + items.append(.separator) + + for preset in presetList { + enum ChatListFilterType { + case generic + case unmuted + case unread + case channels + case groups + case bots + case secretChats + case privateChats + } + let filterType: ChatListFilterType + if preset.includePeers.isEmpty { + if preset.categories == .all { + if preset.excludeRead { + filterType = .unread + } else if preset.excludeMuted { + filterType = .unmuted + } else { + filterType = .generic + } + } else { + if preset.categories == .channels { + filterType = .channels + } else if preset.categories.isSubset(of: [.publicGroups, .privateGroups]) { + filterType = .groups + } else if preset.categories == .bots { + filterType = .bots + } else if preset.categories == .secretChats { + filterType = .secretChats + } else if preset.categories == .privateChats { + filterType = .privateChats + } else { + filterType = .generic + } + } + } else { + filterType = .generic + } + var badge = "" + for item in filterItems { + if item.0.id == preset.id && item.1 != 0 { + badge = "\(item.1)" + } + } + items.append(.action(ContextMenuActionItem(text: preset.title ?? "", badge: badge, icon: { theme in + let imageName: String + switch filterType { + case .generic: + imageName = "Chat/Context Menu/List" + case .unmuted: + imageName = "Chat/Context Menu/Unmute" + case .unread: + imageName = "Chat/Context Menu/MarkAsUnread" + case .channels: + imageName = "Chat/Context Menu/Channels" + case .groups: + imageName = "Chat/Context Menu/Groups" + case .bots: + imageName = "Chat/Context Menu/Bots" + case .secretChats: + imageName = "Chat/Context Menu/Timer" + case .privateChats: + imageName = "Chat/Context Menu/User" + } + return generateTintedImage(image: UIImage(bundleImageName: imageName), color: theme.contextMenu.primaryColor) + }, action: { _, f in + f(.dismissWithoutContent) + guard let strongSelf = self else { + return + } + strongSelf.tabContainerNode.tabSelected?(.filter(preset.id)) + }))) + } + } + + let controller = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .extracted(ChatListTabBarContextExtractedContentSource(controller: strongSelf, sourceNode: sourceNode)), items: .single(items), reactionItems: [], recognizer: nil, gesture: gesture) + strongSelf.context.sharedContext.mainWindow?.presentInGlobalOverlay(controller) + }) + } +} + +private final class ChatListTabBarContextExtractedContentSource: ContextExtractedContentSource { + let keepInPlace: Bool = true + + private let controller: ChatListController + private let sourceNode: ContextExtractedContentContainingNode + + init(controller: ChatListController, sourceNode: ContextExtractedContentContainingNode) { + self.controller = controller + self.sourceNode = sourceNode + } + + func takeView() -> ContextControllerTakeViewInfo? { + return ContextControllerTakeViewInfo(contentContainingNode: self.sourceNode, contentAreaInScreenSpace: UIScreen.main.bounds) + } + + func putBack() -> ContextControllerPutBackViewInfo? { + return ContextControllerPutBackViewInfo(contentAreaInScreenSpace: UIScreen.main.bounds) + } +} + +private final class ChatListHeaderBarContextExtractedContentSource: ContextExtractedContentSource { + let keepInPlace: Bool = false + + private let controller: ChatListController + private let sourceNode: ContextExtractedContentContainingNode + + init(controller: ChatListController, sourceNode: ContextExtractedContentContainingNode) { + self.controller = controller + self.sourceNode = sourceNode + } + + func takeView() -> ContextControllerTakeViewInfo? { + return ContextControllerTakeViewInfo(contentContainingNode: self.sourceNode, contentAreaInScreenSpace: UIScreen.main.bounds) + } + + func putBack() -> ContextControllerPutBackViewInfo? { + return ContextControllerPutBackViewInfo(contentAreaInScreenSpace: UIScreen.main.bounds) } } diff --git a/submodules/ChatListUI/Sources/ChatListControllerNode.swift b/submodules/ChatListUI/Sources/ChatListControllerNode.swift index 9c6b8d1af1..4464bf8cfc 100644 --- a/submodules/ChatListUI/Sources/ChatListControllerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListControllerNode.swift @@ -47,7 +47,7 @@ final class ChatListControllerNode: ASDisplayNode { private let groupId: PeerGroupId private var presentationData: PresentationData - private var chatListEmptyNode: ChatListEmptyNode? + private var chatListEmptyNodeContainer: ChatListEmptyNodeContainer private var chatListEmptyIndicator: ActivityIndicator? let chatListNode: ChatListNode var navigationBar: NavigationBar? @@ -69,6 +69,7 @@ final class ChatListControllerNode: ASDisplayNode { var peerContextAction: ((Peer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?) -> Void)? var dismissSelf: (() -> Void)? var isEmptyUpdated: ((Bool) -> Void)? + var emptyListAction: (() -> Void)? let debugListView = ListView() @@ -77,6 +78,7 @@ final class ChatListControllerNode: ASDisplayNode { self.groupId = groupId self.presentationData = presentationData + self.chatListEmptyNodeContainer = ChatListEmptyNodeContainer(theme: presentationData.theme, strings: presentationData.strings) self.chatListNode = ChatListNode(context: context, groupId: groupId, chatListFilter: filter, previewing: previewing, controlsHistoryPreload: controlsHistoryPreload, mode: .chatList, theme: presentationData.theme, fontSize: presentationData.listsFontSize, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameSortOrder: presentationData.nameSortOrder, nameDisplayOrder: presentationData.nameDisplayOrder, disableAnimations: presentationData.disableAnimations) self.controller = controller @@ -90,51 +92,33 @@ final class ChatListControllerNode: ASDisplayNode { self.backgroundColor = presentationData.theme.chatList.backgroundColor self.addSubnode(self.chatListNode) - self.chatListNode.isEmptyUpdated = { [weak self] isEmptyState in + self.addSubnode(self.chatListEmptyNodeContainer) + self.chatListNode.isEmptyUpdated = { [weak self] isEmptyState, isFilter, transitionDirection, transition in guard let strongSelf = self else { return } switch isEmptyState { - case .empty(false): - if case .group = strongSelf.groupId { - strongSelf.dismissSelf?() - } else if strongSelf.chatListEmptyNode == nil { - let chatListEmptyNode = ChatListEmptyNode(theme: strongSelf.presentationData.theme, strings: strongSelf.presentationData.strings) - strongSelf.chatListEmptyNode = chatListEmptyNode - strongSelf.insertSubnode(chatListEmptyNode, belowSubnode: strongSelf.chatListNode) - if let (layout, navigationHeight, visualNavigationHeight, cleanNavigationBarHeight) = strongSelf.containerLayout { - strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, transition: .immediate) - } - strongSelf.isEmptyUpdated?(true) - } - case .notEmpty(false): - if case .group = strongSelf.groupId { - strongSelf.dismissSelf?() - } - default: - if let chatListEmptyNode = strongSelf.chatListEmptyNode { - strongSelf.chatListEmptyNode = nil - chatListEmptyNode.removeFromSupernode() - } - } - switch isEmptyState { - case .empty(true): - if strongSelf.chatListEmptyIndicator == nil { - let chatListEmptyIndicator = ActivityIndicator(type: .custom(strongSelf.presentationData.theme.list.itemSecondaryTextColor, 22.0, 1.0, false)) - strongSelf.chatListEmptyIndicator = chatListEmptyIndicator - strongSelf.insertSubnode(chatListEmptyIndicator, belowSubnode: strongSelf.chatListNode) - if let (layout, navigationHeight, visualNavigationHeight, cleanNavigationBarHeight) = strongSelf.containerLayout { - strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, transition: .immediate) - } - } - default: - if let chatListEmptyIndicator = strongSelf.chatListEmptyIndicator { - strongSelf.chatListEmptyIndicator = nil - chatListEmptyIndicator.removeFromSupernode() - } + case .empty(false): + if case .group = strongSelf.groupId { + strongSelf.dismissSelf?() + } else { + strongSelf.chatListEmptyNodeContainer.update(state: isEmptyState, isFilter: isFilter, direction: transitionDirection, transition: transition) + } + case .notEmpty(false): + if case .group = strongSelf.groupId { + strongSelf.dismissSelf?() + } else { + strongSelf.chatListEmptyNodeContainer.update(state: isEmptyState, isFilter: isFilter, direction: transitionDirection, transition: transition) + } + default: + strongSelf.chatListEmptyNodeContainer.update(state: isEmptyState, isFilter: isFilter, direction: transitionDirection, transition: transition) } } + self.chatListEmptyNodeContainer.action = { [weak self] in + self?.emptyListAction?() + } + self.addSubnode(self.debugListView) } @@ -151,7 +135,7 @@ final class ChatListControllerNode: ASDisplayNode { self.chatListNode.updateThemeAndStrings(theme: self.presentationData.theme, fontSize: self.presentationData.listsFontSize, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameSortOrder: self.presentationData.nameSortOrder, nameDisplayOrder: self.presentationData.nameDisplayOrder, disableAnimations: self.presentationData.disableAnimations) self.searchDisplayController?.updatePresentationData(presentationData) - self.chatListEmptyNode?.updateThemeAndStrings(theme: self.presentationData.theme, strings: self.presentationData.strings) + self.chatListEmptyNodeContainer.updateThemeAndStrings(theme: self.presentationData.theme, strings: self.presentationData.strings) if let toolbarNode = self.toolbarNode { toolbarNode.updateTheme(TabBarControllerTheme(rootControllerTheme: self.presentationData.theme)) @@ -219,11 +203,9 @@ final class ChatListControllerNode: ASDisplayNode { self.chatListNode.visualInsets = UIEdgeInsets(top: visualNavigationHeight, left: 0.0, bottom: 0.0, right: 0.0) self.chatListNode.updateLayout(transition: transition, updateSizeAndInsets: updateSizeAndInsets) - if let chatListEmptyNode = self.chatListEmptyNode { - let emptySize = CGSize(width: updateSizeAndInsets.size.width, height: updateSizeAndInsets.size.height - updateSizeAndInsets.insets.top - updateSizeAndInsets.insets.bottom) - transition.updateFrame(node: chatListEmptyNode, frame: CGRect(origin: CGPoint(x: 0.0, y: updateSizeAndInsets.insets.top), size: emptySize)) - chatListEmptyNode.updateLayout(size: emptySize, transition: transition) - } + let emptySize = CGSize(width: updateSizeAndInsets.size.width, height: updateSizeAndInsets.size.height - updateSizeAndInsets.insets.top - updateSizeAndInsets.insets.bottom) + transition.updateFrame(node: self.chatListEmptyNodeContainer, frame: CGRect(origin: CGPoint(x: 0.0, y: updateSizeAndInsets.insets.top), size: emptySize)) + self.chatListEmptyNodeContainer.updateLayout(size: emptySize, transition: transition) if let chatListEmptyIndicator = self.chatListEmptyIndicator { let indicatorSize = chatListEmptyIndicator.measure(CGSize(width: 100.0, height: 100.0)) @@ -253,7 +235,9 @@ final class ChatListControllerNode: ASDisplayNode { if let requestAddContact = self?.requestAddContact { requestAddContact(phoneNumber) } - }, peerContextAction: self.peerContextAction), cancel: { [weak self] in + }, peerContextAction: self.peerContextAction, present: { [weak self] c in + self?.controller?.present(c, in: .window(.root)) + }), cancel: { [weak self] in if let requestDeactivateSearch = self?.requestDeactivateSearch { requestDeactivateSearch() } diff --git a/submodules/ChatListUI/Sources/ChatListEmptyNode.swift b/submodules/ChatListUI/Sources/ChatListEmptyNode.swift index 0bd08d0034..4176e5ed4d 100644 --- a/submodules/ChatListUI/Sources/ChatListEmptyNode.swift +++ b/submodules/ChatListUI/Sources/ChatListEmptyNode.swift @@ -3,13 +3,161 @@ import UIKit import AsyncDisplayKit import Display import TelegramPresentationData +import AnimatedStickerNode +import AppBundle +import SolidRoundedButtonNode +import ActivityIndicator + +final class ChatListEmptyNodeContainer: ASDisplayNode { + private var currentNode: ChatListEmptyNode? + + private var theme: PresentationTheme + private var strings: PresentationStrings + private var validLayout: CGSize? + + var action: (() -> Void)? + + init(theme: PresentationTheme, strings: PresentationStrings) { + self.theme = theme + self.strings = strings + + super.init() + } + + func updateThemeAndStrings(theme: PresentationTheme, strings: PresentationStrings) { + self.theme = theme + self.strings = strings + + if let currentNode = self.currentNode { + currentNode.updateThemeAndStrings(theme: theme, strings: strings) + } + } + + func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) { + self.validLayout = size + + if let currentNode = self.currentNode { + currentNode.updateLayout(size: size, transition: transition) + } + } + + func update(state: ChatListNodeEmptyState, isFilter: Bool, direction: ChatListNodePaneSwitchAnimationDirection?, transition: ContainedViewLayoutTransition) { + switch state { + case let .empty(isLoading): + if let direction = direction { + let previousNode = self.currentNode + let currentNode = ChatListEmptyNode(isFilter: isFilter, isLoading: isLoading, theme: self.theme, strings: self.strings, action: { [weak self] in + self?.action?() + }) + self.currentNode = currentNode + if let size = self.validLayout { + currentNode.frame = CGRect(origin: CGPoint(), size: size) + currentNode.updateLayout(size: size, transition: .immediate) + } + self.addSubnode(currentNode) + if case .animated = transition, let size = self.validLayout { + let offset: CGFloat + switch direction { + case .left: + offset = -size.width + case .right: + offset = size.width + } + if let previousNode = previousNode { + previousNode.frame = self.bounds.offsetBy(dx: offset, dy: 0.0) + } + transition.animateHorizontalOffsetAdditive(node: self, offset: offset, completion: { [weak previousNode] in + previousNode?.removeFromSupernode() + }) + } else { + previousNode?.removeFromSupernode() + } + } else { + if let previousNode = self.currentNode, previousNode.isFilter != isFilter { + let currentNode = ChatListEmptyNode(isFilter: isFilter, isLoading: isLoading, theme: self.theme, strings: self.strings, action: { [weak self] in + self?.action?() + }) + self.currentNode = currentNode + if let size = self.validLayout { + currentNode.frame = CGRect(origin: CGPoint(), size: size) + currentNode.updateLayout(size: size, transition: .immediate) + } + self.addSubnode(currentNode) + currentNode.alpha = 0.0 + transition.updateAlpha(node: currentNode, alpha: 1.0) + transition.updateAlpha(node: previousNode, alpha: 0.0, completion: { [weak previousNode] _ in + previousNode?.removeFromSupernode() + }) + } else if let currentNode = self.currentNode { + currentNode.updateIsLoading(isLoading) + } else { + let currentNode = ChatListEmptyNode(isFilter: isFilter, isLoading: isLoading, theme: self.theme, strings: self.strings, action: { [weak self] in + self?.action?() + }) + self.currentNode = currentNode + if let size = self.validLayout { + currentNode.frame = CGRect(origin: CGPoint(), size: size) + currentNode.updateLayout(size: size, transition: .immediate) + } + self.addSubnode(currentNode) + currentNode.alpha = 0.0 + transition.updateAlpha(node: currentNode, alpha: 1.0) + } + } + case .notEmpty: + if let previousNode = self.currentNode { + self.currentNode = nil + if let direction = direction { + if case .animated = transition, let size = self.validLayout { + let offset: CGFloat + switch direction { + case .left: + offset = -size.width + case .right: + offset = size.width + } + previousNode.frame = self.bounds.offsetBy(dx: offset, dy: 0.0) + transition.animateHorizontalOffsetAdditive(node: self, offset: offset, completion: { [weak previousNode] in + previousNode?.removeFromSupernode() + }) + } else { + previousNode.removeFromSupernode() + } + } else { + transition.updateAlpha(node: previousNode, alpha: 0.0, completion: { [weak previousNode] _ in + previousNode?.removeFromSupernode() + }) + } + } + } + } + + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + if let currentNode = self.currentNode { + return currentNode.view.hitTest(self.view.convert(point, to: currentNode.view), with: event) + } + return nil + } +} final class ChatListEmptyNode: ASDisplayNode { + let isFilter: Bool + private(set) var isLoading: Bool private let textNode: ImmediateTextNode + private let animationNode: AnimatedStickerNode + private let buttonNode: SolidRoundedButtonNode + private let activityIndicator: ActivityIndicator + + private var animationSize: CGSize = CGSize() private var validLayout: CGSize? - init(theme: PresentationTheme, strings: PresentationStrings) { + init(isFilter: Bool, isLoading: Bool, theme: PresentationTheme, strings: PresentationStrings, action: @escaping () -> Void) { + self.isFilter = isFilter + self.isLoading = isLoading + + self.animationNode = AnimatedStickerNode() + self.textNode = ImmediateTextNode() self.textNode.displaysAsynchronously = false self.textNode.maximumNumberOfLines = 0 @@ -17,28 +165,108 @@ final class ChatListEmptyNode: ASDisplayNode { self.textNode.textAlignment = .center self.textNode.lineSpacing = 0.1 + self.buttonNode = SolidRoundedButtonNode(title: isFilter ? strings.ChatList_EmptyChatListEditFilter : strings.ChatList_EmptyChatListNewMessage, theme: SolidRoundedButtonTheme(backgroundColor: theme.list.itemCheckColors.fillColor, foregroundColor: theme.list.itemCheckColors.foregroundColor), height: 50.0, cornerRadius: 10.0, gloss: false) + + self.activityIndicator = ActivityIndicator(type: .custom(theme.list.itemAccentColor, 22.0, 1.0, false)) + super.init() + self.addSubnode(self.animationNode) self.addSubnode(self.textNode) + self.addSubnode(self.buttonNode) + + let animationName: String + if isFilter { + animationName = "ChatListFilterEmpty" + } else { + animationName = "ChatListEmpty" + } + if let path = getAppBundle().path(forResource: animationName, ofType: "tgs") { + self.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(path: path), width: 248, height: 248, playbackMode: .once, mode: .direct) + self.animationSize = CGSize(width: 124.0, height: 124.0) + self.animationNode.visibility = true + } + + self.buttonNode.pressed = { + action() + } + + self.animationNode.isHidden = self.isLoading + self.textNode.isHidden = self.isLoading + self.buttonNode.isHidden = self.isLoading + self.activityIndicator.isHidden = !self.isLoading self.updateThemeAndStrings(theme: theme, strings: strings) } func updateThemeAndStrings(theme: PresentationTheme, strings: PresentationStrings) { - let string = NSMutableAttributedString() - string.append(NSAttributedString(string: strings.DialogList_NoMessagesTitle + "\n", font: Font.medium(17.0), textColor: theme.list.itemSecondaryTextColor, paragraphAlignment: .center)) - string.append(NSAttributedString(string: strings.DialogList_NoMessagesText, font: Font.regular(16.0), textColor: theme.list.itemSecondaryTextColor, paragraphAlignment: .center)) + let string = NSMutableAttributedString(string: self.isFilter ? strings.ChatList_EmptyChatFilterList : strings.ChatList_EmptyChatList, font: Font.medium(17.0), textColor: theme.list.itemPrimaryTextColor) self.textNode.attributedText = string + self.buttonNode.updateTheme(SolidRoundedButtonTheme(backgroundColor: theme.list.itemCheckColors.fillColor, foregroundColor: theme.list.itemCheckColors.foregroundColor)) + + self.activityIndicator.type = .custom(theme.list.itemAccentColor, 22.0, 1.0, false) + if let size = self.validLayout { self.updateLayout(size: size, transition: .immediate) } } + func updateIsLoading(_ isLoading: Bool) { + if self.isLoading == isLoading { + return + } + self.isLoading = isLoading + self.animationNode.isHidden = self.isLoading + self.textNode.isHidden = self.isLoading + self.buttonNode.isHidden = self.isLoading + self.activityIndicator.isHidden = !self.isLoading + } + func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) { self.validLayout = size + let indicatorSize = self.activityIndicator.measure(CGSize(width: 100.0, height: 100.0)) + transition.updateFrame(node: self.activityIndicator, frame: CGRect(origin: CGPoint(x: floor((size.width - indicatorSize.width) / 2.0), y: floor((size.height - indicatorSize.height - 50.0) / 2.0)), size: indicatorSize)) + + let animationSpacing: CGFloat = 10.0 + let buttonSpacing: CGFloat = 24.0 + let buttonSideInset: CGFloat = 16.0 + let textSize = self.textNode.updateLayout(CGSize(width: size.width - 40.0, height: size.height)) - transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: floor((size.width - textSize.width) / 2.0), y: floor((size.height - textSize.height) / 2.0)), size: textSize)) + + let buttonWidth = min(size.width - buttonSideInset * 2.0, 280.0) + let buttonSize = CGSize(width: buttonWidth, height: 50.0) + + let contentHeight = self.animationSize.height + animationSpacing + textSize.height + buttonSpacing + buttonSize.height + var contentOffset: CGFloat = 0.0 + if size.height < contentHeight { + contentOffset = -self.animationSize.height - animationSpacing + 44.0 + transition.updateAlpha(node: self.animationNode, alpha: 0.0) + } else { + contentOffset = -40.0 + transition.updateAlpha(node: self.animationNode, alpha: 1.0) + } + + let animationFrame = CGRect(origin: CGPoint(x: floor((size.width - self.animationSize.width) / 2.0), y: floor((size.height - contentHeight) / 2.0) + contentOffset), size: self.animationSize) + let textFrame = CGRect(origin: CGPoint(x: floor((size.width - textSize.width) / 2.0), y: animationFrame.maxY + animationSpacing), size: textSize) + let buttonFrame = CGRect(origin: CGPoint(x: floor((size.width - buttonSize.width) / 2.0), y: textFrame.maxY + buttonSpacing), size: buttonSize) + + if !self.animationSize.width.isZero { + self.animationNode.updateLayout(size: self.animationSize) + transition.updateFrame(node: self.animationNode, frame: animationFrame) + } + + transition.updateFrame(node: self.textNode, frame: textFrame) + + self.buttonNode.updateLayout(width: buttonFrame.width, transition: transition) + transition.updateFrame(node: self.buttonNode, frame: buttonFrame) + } + + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + if self.buttonNode.frame.contains(point) { + return self.buttonNode.view.hitTest(self.view.convert(point, to: self.buttonNode.view), with: event) + } + return nil } } diff --git a/submodules/ChatListUI/Sources/ChatListFilterPresetController.swift b/submodules/ChatListUI/Sources/ChatListFilterPresetController.swift index 49fd02eac3..c9bdcc3c23 100644 --- a/submodules/ChatListUI/Sources/ChatListFilterPresetController.swift +++ b/submodules/ChatListUI/Sources/ChatListFilterPresetController.swift @@ -245,6 +245,7 @@ private struct ChatListFilterPresetControllerState: Equatable { } } +//TODO:localization private func chatListFilterPresetControllerEntries(presentationData: PresentationData, state: ChatListFilterPresetControllerState, peers: [RenderedPeer]) -> [ChatListFilterPresetEntry] { var entries: [ChatListFilterPresetEntry] = [] @@ -274,6 +275,41 @@ private func chatListFilterPresetControllerEntries(presentationData: Presentatio return entries } +func chatListFilterAddChatsController(context: AccountContext, filter: ChatListFilter) -> ViewController { + let controller = context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: context, mode: .peerSelection(searchChatList: true, searchGroups: true, searchChannels: true), options: [])) + controller.navigationPresentation = .modal + let _ = (controller.result + |> take(1) + |> deliverOnMainQueue).start(next: { [weak controller] peerIds in + let _ = (updateChatListFilterSettingsInteractively(postbox: context.account.postbox, { settings in + var settings = settings + for i in 0 ..< settings.filters.count { + if settings.filters[i].id == filter.id { + let previousIncludePeers = settings.filters[i].includePeers + + var chatPeerIds: [PeerId] = [] + for peerId in peerIds { + switch peerId { + case let .peer(id): + chatPeerIds.append(id) + default: + break + } + } + settings.filters[i].includePeers = chatPeerIds + previousIncludePeers.filter { peerId in + return !chatPeerIds.contains(peerId) + } + } + } + return settings + }) + |> deliverOnMainQueue).start(next: { settings in + controller?.dismiss() + }) + }) + return controller +} + func chatListFilterPresetController(context: AccountContext, currentPreset: ChatListFilter?, updated: @escaping ([ChatListFilter]) -> Void) -> ViewController { let initialName: String if let currentPreset = currentPreset { @@ -302,7 +338,7 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat updateState(f) }, openAddPeer: { - let controller = context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: context, mode: .peerSelection(searchChatList: true, searchGroups: true), options: [])) + let controller = context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: context, mode: .peerSelection(searchChatList: true, searchGroups: true, searchChannels: true), options: [])) addPeerDisposable.set((controller.result |> take(1) |> deliverOnMainQueue).start(next: { [weak controller] peerIds in @@ -313,7 +349,7 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat switch peerId { case let .peer(id): if !state.additionallyIncludePeers.contains(id) { - state.additionallyIncludePeers.append(id) + state.additionallyIncludePeers.insert(id, at: 0) } default: break @@ -371,7 +407,7 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat let leftNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Cancel), style: .regular, enabled: true, action: { dismissImpl?() }) - let rightNavigationButton = ItemListNavigationButton(content: .text("Create"), style: .bold, enabled: state.isComplete, action: { + let rightNavigationButton = ItemListNavigationButton(content: .text(currentPreset == nil ? presentationData.strings.Common_Create : presentationData.strings.Common_Done), style: .bold, enabled: state.isComplete, action: { let state = stateValue.with { $0 } let preset = ChatListFilter(id: currentPreset?.id ?? -1, title: state.name, categories: state.includeCategories, excludeMuted: state.excludeMuted, excludeRead: state.excludeRead, includePeers: state.additionallyIncludePeers) let _ = (updateChatListFilterSettingsInteractively(postbox: context.account.postbox, { settings in diff --git a/submodules/ChatListUI/Sources/ChatListFilterPresetListController.swift b/submodules/ChatListUI/Sources/ChatListFilterPresetListController.swift index 4099b13d37..53b5d67994 100644 --- a/submodules/ChatListUI/Sources/ChatListFilterPresetListController.swift +++ b/submodules/ChatListUI/Sources/ChatListFilterPresetListController.swift @@ -14,15 +14,13 @@ import ItemListPeerActionItem private final class ChatListFilterPresetListControllerArguments { let context: AccountContext - let toggleEnableTabs: (Bool) -> Void let openPreset: (ChatListFilter) -> Void let addNew: () -> Void let setItemWithRevealedOptions: (Int32?, Int32?) -> Void let removePreset: (Int32) -> Void - init(context: AccountContext, toggleEnableTabs: @escaping (Bool) -> Void, openPreset: @escaping (ChatListFilter) -> Void, addNew: @escaping () -> Void, setItemWithRevealedOptions: @escaping (Int32?, Int32?) -> Void, removePreset: @escaping (Int32) -> Void) { + init(context: AccountContext, openPreset: @escaping (ChatListFilter) -> Void, addNew: @escaping () -> Void, setItemWithRevealedOptions: @escaping (Int32?, Int32?) -> Void, removePreset: @escaping (Int32) -> Void) { self.context = context - self.toggleEnableTabs = toggleEnableTabs self.openPreset = openPreset self.addNew = addNew self.setItemWithRevealedOptions = setItemWithRevealedOptions @@ -31,7 +29,6 @@ private final class ChatListFilterPresetListControllerArguments { } private enum ChatListFilterPresetListSection: Int32 { - case tabs case list } @@ -48,8 +45,6 @@ private func stringForUserCount(_ peers: [PeerId: SelectivePrivacyPeer], strings } private enum ChatListFilterPresetListEntryStableId: Hashable { - case displayTabs - case displayTabsFooter case listHeader case preset(Int32) case addItem @@ -57,8 +52,6 @@ private enum ChatListFilterPresetListEntryStableId: Hashable { } private enum ChatListFilterPresetListEntry: ItemListNodeEntry { - case displayTabs(String, Bool) - case displayTabsFooter(String) case listHeader(String) case preset(index: Int, title: String?, preset: ChatListFilter, canBeReordered: Bool, canBeDeleted: Bool, isEditing: Bool) case addItem(text: String, isEditing: Bool) @@ -66,8 +59,6 @@ private enum ChatListFilterPresetListEntry: ItemListNodeEntry { var section: ItemListSectionId { switch self { - case .displayTabs, .displayTabsFooter: - return ChatListFilterPresetListSection.tabs.rawValue case .listHeader, .preset, .addItem, .listFooter: return ChatListFilterPresetListSection.list.rawValue } @@ -75,10 +66,6 @@ private enum ChatListFilterPresetListEntry: ItemListNodeEntry { var sortId: Int { switch self { - case .displayTabs: - return 0 - case .displayTabsFooter: - return 1 case .listHeader: return 2 case let .preset(preset): @@ -92,10 +79,6 @@ private enum ChatListFilterPresetListEntry: ItemListNodeEntry { var stableId: ChatListFilterPresetListEntryStableId { switch self { - case .displayTabs: - return .displayTabs - case .displayTabsFooter: - return .displayTabsFooter case .listHeader: return .listHeader case let .preset(preset): @@ -114,12 +97,6 @@ private enum ChatListFilterPresetListEntry: ItemListNodeEntry { func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem { let arguments = arguments as! ChatListFilterPresetListControllerArguments switch self { - case let .displayTabs(title, value): - return ItemListSwitchItem(presentationData: presentationData, title: title, value: value, sectionId: self.section, style: .blocks, updated: { value in - arguments.toggleEnableTabs(value) - }) - case let .displayTabsFooter(text): - return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section) case let .listHeader(text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, multiline: true, sectionId: self.section) case let .preset(index, title, preset, canBeReordered, canBeDeleted, isEditing): @@ -147,15 +124,14 @@ private struct ChatListFilterPresetListControllerState: Equatable { private func chatListFilterPresetListControllerEntries(presentationData: PresentationData, state: ChatListFilterPresetListControllerState, filtersState: ChatListFiltersState, settings: ChatListFilterSettings) -> [ChatListFilterPresetListEntry] { var entries: [ChatListFilterPresetListEntry] = [] - - entries.append(.displayTabs("Show Tabs", settings.displayTabs)) - entries.append(.displayTabsFooter("Display filter tabs on the main screen for quick switching.")) entries.append(.listHeader("FILTERS")) for preset in filtersState.filters { entries.append(.preset(index: entries.count, title: preset.title, preset: preset, canBeReordered: filtersState.filters.count > 1, canBeDeleted: true, isEditing: state.isEditing)) } - entries.append(.addItem(text: "Create New Filter", isEditing: state.isEditing)) + if filtersState.filters.count < 10 { + entries.append(.addItem(text: "Create New Filter", isEditing: state.isEditing)) + } entries.append(.listFooter("Tap \"Edit\" to change the order or delete filters.")) return entries @@ -173,15 +149,7 @@ func chatListFilterPresetListController(context: AccountContext, updated: @escap var pushControllerImpl: ((ViewController) -> Void)? var presentControllerImpl: ((ViewController, Any?) -> Void)? - let arguments = ChatListFilterPresetListControllerArguments(context: context, toggleEnableTabs: { value in - let _ = context.account.postbox.transaction({ transaction -> Void in - let _ = updateChatListFilterSettings(transaction: transaction, { settings in - var settings = settings - settings.displayTabs = value - return settings - }) - }).start() - }, openPreset: { preset in + let arguments = ChatListFilterPresetListControllerArguments(context: context, openPreset: { preset in pushControllerImpl?(chatListFilterPresetController(context: context, currentPreset: preset, updated: updated)) }, addNew: { pushControllerImpl?(chatListFilterPresetController(context: context, currentPreset: nil, updated: updated)) diff --git a/submodules/ChatListUI/Sources/ChatListFilterTabContainerNode.swift b/submodules/ChatListUI/Sources/ChatListFilterTabContainerNode.swift index c7b610e409..4d8a690927 100644 --- a/submodules/ChatListUI/Sources/ChatListFilterTabContainerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListFilterTabContainerNode.swift @@ -10,22 +10,36 @@ import TelegramPresentationData private final class ItemNode: ASDisplayNode { private let pressed: () -> Void + private let extractedContainerNode: ContextExtractedContentContainingNode + private let containerNode: ContextControllerSourceNode + private let titleNode: ImmediateTextNode + private let extractedTitleNode: ImmediateTextNode + private let badgeContainerNode: ASDisplayNode private let badgeTextNode: ImmediateTextNode private let badgeBackgroundNode: ASImageNode private let buttonNode: HighlightTrackingButtonNode private var isSelected: Bool = false - private var unreadCount: Int = 0 + private(set) var unreadCount: Int = 0 private var theme: PresentationTheme? - init(pressed: @escaping () -> Void) { + init(pressed: @escaping () -> Void, contextGesture: @escaping (ContextExtractedContentContainingNode, ContextGesture) -> Void) { self.pressed = pressed + self.extractedContainerNode = ContextExtractedContentContainingNode() + self.containerNode = ContextControllerSourceNode() + self.titleNode = ImmediateTextNode() self.titleNode.displaysAsynchronously = false + self.extractedTitleNode = ImmediateTextNode() + self.extractedTitleNode.displaysAsynchronously = false + self.extractedTitleNode.alpha = 0.0 + + self.badgeContainerNode = ASDisplayNode() + self.badgeTextNode = ImmediateTextNode() self.badgeTextNode.displaysAsynchronously = false @@ -37,57 +51,106 @@ private final class ItemNode: ASDisplayNode { super.init() - self.addSubnode(self.titleNode) - self.addSubnode(self.badgeBackgroundNode) - self.addSubnode(self.badgeTextNode) - self.addSubnode(self.buttonNode) + self.extractedContainerNode.contentNode.addSubnode(self.titleNode) + self.extractedContainerNode.contentNode.addSubnode(self.extractedTitleNode) + self.badgeContainerNode.addSubnode(self.badgeBackgroundNode) + self.badgeContainerNode.addSubnode(self.badgeTextNode) + self.extractedContainerNode.contentNode.addSubnode(self.badgeContainerNode) + self.extractedContainerNode.contentNode.addSubnode(self.buttonNode) + + self.containerNode.addSubnode(self.extractedContainerNode) + self.containerNode.targetNodeForActivationProgress = self.extractedContainerNode.contentNode + self.addSubnode(self.containerNode) self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside) + + self.containerNode.activated = { [weak self] gesture in + guard let strongSelf = self else { + return + } + contextGesture(strongSelf.extractedContainerNode, gesture) + } + + self.extractedContainerNode.willUpdateIsExtractedToContextPreview = { [weak self] isExtracted, transition in + guard let strongSelf = self else { + return + } + transition.updateAlpha(node: strongSelf.titleNode, alpha: isExtracted ? 0.0 : 1.0) + transition.updateAlpha(node: strongSelf.extractedTitleNode, alpha: isExtracted ? 1.0 : 0.0) + } } @objc private func buttonPressed() { self.pressed() } - func updateText(title: String, unreadCount: Int, isSelected: Bool, presentationData: PresentationData) { + func updateText(title: String, unreadCount: Int, isNoFilter: Bool, isSelected: Bool, presentationData: PresentationData) { if self.theme !== presentationData.theme { self.theme = presentationData.theme - self.badgeBackgroundNode.image = generateStretchableFilledCircleImage(diameter: 18.0, color: presentationData.theme.rootController.navigationBar.badgeBackgroundColor) + self.badgeBackgroundNode.image = generateStretchableFilledCircleImage(diameter: 18.0, color: presentationData.theme.list.itemCheckColors.fillColor) } + self.containerNode.isGestureEnabled = !isNoFilter + self.isSelected = isSelected self.unreadCount = unreadCount self.titleNode.attributedText = NSAttributedString(string: title, font: Font.medium(14.0), textColor: isSelected ? presentationData.theme.list.itemAccentColor : presentationData.theme.list.itemSecondaryTextColor) + self.extractedTitleNode.attributedText = NSAttributedString(string: title, font: Font.medium(14.0), textColor: presentationData.theme.contextMenu.extractedContentTintColor) if unreadCount != 0 { - self.badgeTextNode.attributedText = NSAttributedString(string: "\(unreadCount)", font: Font.regular(14.0), textColor: presentationData.theme.rootController.navigationBar.badgeTextColor) + self.badgeTextNode.attributedText = NSAttributedString(string: "\(unreadCount)", font: Font.regular(14.0), textColor: presentationData.theme.list.itemCheckColors.foregroundColor) } } func updateLayout(height: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat { let titleSize = self.titleNode.updateLayout(CGSize(width: 200.0, height: .greatestFiniteMagnitude)) + let _ = self.extractedTitleNode.updateLayout(CGSize(width: 200.0, height: .greatestFiniteMagnitude)) self.titleNode.frame = CGRect(origin: CGPoint(x: 0.0, y: floor((height - titleSize.height) / 2.0)), size: titleSize) + self.extractedTitleNode.frame = CGRect(origin: CGPoint(x: 0.0, y: floor((height - titleSize.height) / 2.0)), size: titleSize) let badgeSize = self.badgeTextNode.updateLayout(CGSize(width: 200.0, height: .greatestFiniteMagnitude)) - let badgeInset: CGFloat = 5.0 + let badgeInset: CGFloat = 4.0 let badgeBackgroundFrame = CGRect(origin: CGPoint(x: titleSize.width + 5.0, y: floor((height - 18.0) / 2.0)), size: CGSize(width: max(18.0, badgeSize.width + badgeInset * 2.0), height: 18.0)) - self.badgeBackgroundNode.frame = badgeBackgroundFrame - self.badgeTextNode.frame = CGRect(origin: CGPoint(x: badgeBackgroundFrame.minX + floor((badgeBackgroundFrame.width - badgeSize.width) / 2.0), y: badgeBackgroundFrame.minY + floor((badgeBackgroundFrame.height - badgeSize.height) / 2.0)), size: badgeSize) + self.badgeContainerNode.frame = badgeBackgroundFrame + self.badgeBackgroundNode.frame = CGRect(origin: CGPoint(), size: badgeBackgroundFrame.size) + self.badgeTextNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((badgeBackgroundFrame.width - badgeSize.width) / 2.0), y: floor((badgeBackgroundFrame.height - badgeSize.height) / 2.0)), size: badgeSize) if self.unreadCount == 0 { - self.badgeBackgroundNode.alpha = 0.0 - self.badgeTextNode.alpha = 0.0 + self.badgeContainerNode.alpha = 0.0 return titleSize.width } else { - self.badgeBackgroundNode.alpha = 1.0 - self.badgeTextNode.alpha = 1.0 + self.badgeContainerNode.alpha = 1.0 return badgeBackgroundFrame.maxX } } func updateArea(size: CGSize, sideInset: CGFloat) { self.buttonNode.frame = CGRect(origin: CGPoint(x: -sideInset, y: 0.0), size: CGSize(width: size.width + sideInset * 2.0, height: size.height)) + + self.extractedContainerNode.frame = CGRect(origin: CGPoint(), size: size) + self.extractedContainerNode.contentNode.frame = CGRect(origin: CGPoint(), size: size) + self.extractedContainerNode.contentRect = CGRect(origin: CGPoint(), size: size) + self.containerNode.frame = CGRect(origin: CGPoint(), size: size) + + self.hitTestSlop = UIEdgeInsets(top: 0.0, left: -sideInset, bottom: 0.0, right: -sideInset) + self.extractedContainerNode.hitTestSlop = self.hitTestSlop + self.extractedContainerNode.contentNode.hitTestSlop = self.hitTestSlop + self.containerNode.hitTestSlop = self.hitTestSlop + } + + func animateBadgeIn() { + let transition: ContainedViewLayoutTransition = .animated(duration: 0.4, curve: .spring) + self.badgeContainerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) + ContainedViewLayoutTransition.immediate.updateSublayerTransformScale(node: self.badgeContainerNode, scale: 0.1) + transition.updateSublayerTransformScale(node: self.badgeContainerNode, scale: 1.0) + } + + func animateBadgeOut() { + let transition: ContainedViewLayoutTransition = .animated(duration: 0.4, curve: .spring) + self.badgeContainerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25) + ContainedViewLayoutTransition.immediate.updateSublayerTransformScale(node: self.badgeContainerNode, scale: 1.0) + transition.updateSublayerTransformScale(node: self.badgeContainerNode, scale: 0.1) } } @@ -97,7 +160,7 @@ enum ChatListFilterTabEntryId: Hashable { } enum ChatListFilterTabEntry: Equatable { - case all + case all(unreadCount: Int) case filter(id: Int32, text: String, unreadCount: Int) var id: ChatListFilterTabEntryId { @@ -162,6 +225,7 @@ final class ChatListFilterTabContainerNode: ASDisplayNode { var tabSelected: ((ChatListFilterTabEntryId) -> Void)? var addFilter: (() -> Void)? + var contextGesture: ((Int32, ContextExtractedContentContainingNode, ContextGesture) -> Void)? private var currentParams: (size: CGSize, sideInset: CGFloat, filters: [ChatListFilterTabEntry], selectedFilter: ChatListFilterTabEntryId?, presentationData: PresentationData)? @@ -195,6 +259,11 @@ final class ChatListFilterTabContainerNode: ASDisplayNode { func update(size: CGSize, sideInset: CGFloat, filters: [ChatListFilterTabEntry], selectedFilter: ChatListFilterTabEntryId?, presentationData: PresentationData, transition: ContainedViewLayoutTransition) { let focusOnSelectedFilter = self.currentParams?.selectedFilter != selectedFilter + var previousSelectedAbsFrame: CGRect? + let previousScrollBounds = self.scrollNode.bounds + if let currentSelectedFilter = self.currentParams?.selectedFilter, let itemNode = self.itemNodes[currentSelectedFilter] { + previousSelectedAbsFrame = itemNode.frame.offsetBy(dx: -self.scrollNode.bounds.minX, dy: 0.0) + } if self.currentParams?.presentationData.theme !== presentationData.theme { self.selectedLineNode.image = generateImage(CGSize(width: 7.0, height: 4.0), rotatedContext: { size, context in @@ -208,6 +277,13 @@ final class ChatListFilterTabContainerNode: ASDisplayNode { transition.updateFrame(node: self.scrollNode, frame: CGRect(origin: CGPoint(), size: size)) + enum BadgeAnimation { + case `in` + case out + } + + var badgeAnimations: [ChatListFilterTabEntryId: BadgeAnimation] = [:] + for filter in filters { let itemNode: ItemNode var wasAdded = false @@ -217,17 +293,35 @@ final class ChatListFilterTabContainerNode: ASDisplayNode { wasAdded = true itemNode = ItemNode(pressed: { [weak self] in self?.tabSelected?(filter.id) + }, contextGesture: { [weak self] sourceNode, gesture in + guard let strongSelf = self else { + return + } + switch filter { + case let .filter(filter): + strongSelf.scrollNode.view.panGestureRecognizer.isEnabled = false + strongSelf.scrollNode.view.panGestureRecognizer.isEnabled = true + strongSelf.scrollNode.view.setContentOffset(strongSelf.scrollNode.view.contentOffset, animated: false) + strongSelf.contextGesture?(filter.id, sourceNode, gesture) + default: + break + } }) self.itemNodes[filter.id] = itemNode } let unreadCount: Int + var isNoFilter: Bool = false switch filter { - case .all: - unreadCount = 0 + case let .all(count): + unreadCount = count + isNoFilter = true case let .filter(filter): unreadCount = filter.unreadCount } - itemNode.updateText(title: filter.title(strings: presentationData.strings), unreadCount: unreadCount, isSelected: selectedFilter == filter.id, presentationData: presentationData) + if !wasAdded && (itemNode.unreadCount != 0) != (unreadCount != 0) { + badgeAnimations[filter.id] = (unreadCount != 0) ? .in : .out + } + itemNode.updateText(title: filter.title(strings: presentationData.strings), unreadCount: unreadCount, isNoFilter: isNoFilter, isSelected: selectedFilter == filter.id, presentationData: presentationData) } var removeKeys: [ChatListFilterTabEntryId] = [] for (id, _) in self.itemNodes { @@ -257,6 +351,15 @@ final class ChatListFilterTabContainerNode: ASDisplayNode { let paneNodeSize = CGSize(width: paneNodeWidth, height: size.height) tabSizes.append((paneNodeSize, itemNode, wasAdded)) totalRawTabSize += paneNodeSize.width + + if case .animated = transition, let badgeAnimation = badgeAnimations[filter.id] { + switch badgeAnimation { + case .in: + itemNode.animateBadgeIn() + case .out: + itemNode.animateBadgeOut() + } + } } let minSpacing: CGFloat = 30.0 @@ -271,7 +374,7 @@ final class ChatListFilterTabContainerNode: ASDisplayNode { paneNode.alpha = 0.0 transition.updateAlpha(node: paneNode, alpha: 1.0) } else { - transition.updateFrameAdditiveToCenter(node: paneNode, frame: paneFrame) + transition.updateFrameAdditive(node: paneNode, frame: paneFrame) } paneNode.updateArea(size: paneFrame.size, sideInset: minSpacing / 2.0) paneNode.hitTestSlop = UIEdgeInsets(top: 0.0, left: -minSpacing / 2.0, bottom: 0.0, right: -minSpacing / 2.0) @@ -286,7 +389,7 @@ final class ChatListFilterTabContainerNode: ASDisplayNode { self.addNode.update(size: addSize, theme: presentationData.theme) leftOffset += addSize.width + minSpacing - self.scrollNode.view.contentSize = CGSize(width: leftOffset - minSpacing + sideInset, height: size.height) + self.scrollNode.view.contentSize = CGSize(width: leftOffset - minSpacing + sideInset - 5.0, height: size.height) let transitionFraction: CGFloat = 0.0 var selectedFrame: CGRect? @@ -328,6 +431,16 @@ final class ChatListFilterTabContainerNode: ASDisplayNode { let contentOffsetX = max(0.0, min(self.scrollNode.view.contentSize.width - self.scrollNode.bounds.width, floor(selectedFrame.midX - self.scrollNode.bounds.width / 2.0))) transition.updateBounds(node: self.scrollNode, bounds: CGRect(origin: CGPoint(x: contentOffsetX, y: 0.0), size: self.scrollNode.bounds.size)) } + } else if !wasAdded, let previousSelectedAbsFrame = previousSelectedAbsFrame { + let contentOffsetX: CGFloat + if previousScrollBounds.minX.isZero { + contentOffsetX = 0.0 + } else if previousScrollBounds.maxX == previousScrollBounds.width { + contentOffsetX = self.scrollNode.view.contentSize.width - self.scrollNode.bounds.width + } else { + contentOffsetX = selectedFrame.midX - previousSelectedAbsFrame.midX + } + transition.updateBounds(node: self.scrollNode, bounds: CGRect(origin: CGPoint(x: contentOffsetX, y: 0.0), size: self.scrollNode.bounds.size)) } } else { self.selectedLineNode.isHidden = true diff --git a/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift b/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift index 0826e73dfe..4b60962349 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift @@ -484,7 +484,7 @@ public enum ChatListSearchEntry: Comparable, Identifiable { } }) case let .message(message, peer, readState, presentationData): - return ChatListItem(presentationData: presentationData, context: context, peerGroupId: .root, index: ChatListIndex(pinningIndex: nil, messageIndex: message.index), content: .peer(message: message, peer: peer, combinedReadState: readState, notificationSettings: nil, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: true, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: enableHeaders ? ChatListSearchItemHeader(type: .messages, theme: presentationData.theme, strings: presentationData.strings, actionTitle: nil, action: nil) : nil, enableContextActions: false, hiddenOffset: false, interaction: interaction) + return ChatListItem(presentationData: presentationData, context: context, peerGroupId: .root, isInFilter: false, index: ChatListIndex(pinningIndex: nil, messageIndex: message.index), content: .peer(message: message, peer: peer, combinedReadState: readState, notificationSettings: nil, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: true, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: enableHeaders ? ChatListSearchItemHeader(type: .messages, theme: presentationData.theme, strings: presentationData.strings, actionTitle: nil, action: nil) : nil, enableContextActions: false, hiddenOffset: false, interaction: interaction) case let .addContact(phoneNumber, theme, strings): return ContactsAddItem(theme: theme, strings: strings, phoneNumber: phoneNumber, header: ChatListSearchItemHeader(type: .phoneNumber, theme: theme, strings: strings, actionTitle: nil, action: nil), action: { interaction.addContact(phoneNumber) @@ -627,7 +627,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo private let filter: ChatListNodePeersFilter - public init(context: AccountContext, filter: ChatListNodePeersFilter, groupId: PeerGroupId, openPeer originalOpenPeer: @escaping (Peer, Bool) -> Void, openDisabledPeer: @escaping (Peer) -> Void, openRecentPeerOptions: @escaping (Peer) -> Void, openMessage originalOpenMessage: @escaping (Peer, MessageId) -> Void, addContact: ((String) -> Void)?, peerContextAction: ((Peer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?) -> Void)?) { + public init(context: AccountContext, filter: ChatListNodePeersFilter, groupId: PeerGroupId, openPeer originalOpenPeer: @escaping (Peer, Bool) -> Void, openDisabledPeer: @escaping (Peer) -> Void, openRecentPeerOptions: @escaping (Peer) -> Void, openMessage originalOpenMessage: @escaping (Peer, MessageId) -> Void, addContact: ((String) -> Void)?, peerContextAction: ((Peer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?) -> Void)?, present: @escaping (ViewController) -> Void) { self.context = context self.filter = filter self.dimNode = ASDisplayNode() @@ -1043,6 +1043,8 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo case .groupReference: gesture?.cancel() } + }, present: { [weak self] c in + present(c) }) self.interaction = interaction @@ -1390,8 +1392,25 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo } private func clearRecentSearch() { - let _ = (clearRecentlySearchedPeers(postbox: self.context.account.postbox) - |> deliverOnMainQueue).start() + let presentationData = self.presentationData + let actionSheet = ActionSheetController(presentationData: presentationData) + actionSheet.setItemGroups([ActionSheetItemGroup(items: [ + ActionSheetButtonItem(title: presentationData.strings.WebSearch_RecentSectionClear, color: .destructive, action: { [weak self, weak actionSheet] in + actionSheet?.dismissAnimated() + + guard let strongSelf = self else { + return + } + let _ = (clearRecentlySearchedPeers(postbox: strongSelf.context.account.postbox) + |> deliverOnMainQueue).start() + }) + ]), ActionSheetItemGroup(items: [ + ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in + actionSheet?.dismissAnimated() + }) + ])]) + self.view.window?.endEditing(true) + self.interaction?.present(actionSheet) } override public func scrollToTop() { diff --git a/submodules/ChatListUI/Sources/Node/ChatListEmptyHeaderItem.swift b/submodules/ChatListUI/Sources/Node/ChatListEmptyHeaderItem.swift new file mode 100644 index 0000000000..f8c4dadded --- /dev/null +++ b/submodules/ChatListUI/Sources/Node/ChatListEmptyHeaderItem.swift @@ -0,0 +1,82 @@ +import Foundation +import UIKit +import AsyncDisplayKit +import Postbox +import Display +import SwiftSignalKit +import TelegramPresentationData +import ListSectionHeaderNode +import AppBundle + +class ChatListEmptyHeaderItem: ListViewItem { + let selectable: Bool = false + + init() { + } + + func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal?, (ListViewItemApply) -> Void)) -> Void) { + async { + let node = ChatListEmptyHeaderItemNode() + + let (nodeLayout, apply) = node.asyncLayout()(self, params, false) + + node.insets = nodeLayout.insets + node.contentSize = nodeLayout.contentSize + + Queue.mainQueue().async { + completion(node, { + return (nil, { _ in + apply() + }) + }) + } + } + } + + func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) { + Queue.mainQueue().async { + assert(node() is ChatListEmptyHeaderItemNode) + if let nodeValue = node() as? ChatListEmptyHeaderItemNode { + + let layout = nodeValue.asyncLayout() + async { + let (nodeLayout, apply) = layout(self, params, nextItem == nil) + Queue.mainQueue().async { + completion(nodeLayout, { _ in + apply() + }) + } + } + } + } + } +} + +class ChatListEmptyHeaderItemNode: ListViewItemNode { + private var item: ChatListEmptyHeaderItem? + + required init() { + super.init(layerBacked: false, dynamicBounce: false) + } + + override func layoutForParams(_ params: ListViewItemLayoutParams, item: ListViewItem, previousItem: ListViewItem?, nextItem: ListViewItem?) { + let layout = self.asyncLayout() + let (_, apply) = layout(item as! ChatListEmptyHeaderItem, params, nextItem == nil) + apply() + } + + func asyncLayout() -> (_ item: ChatListEmptyHeaderItem, _ params: ListViewItemLayoutParams, _ isLast: Bool) -> (ListViewItemNodeLayout, () -> Void) { + return { item, params, last in + let layout = ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: 0.0), insets: UIEdgeInsets()) + + return (layout, { [weak self] in + if let strongSelf = self { + strongSelf.item = item + + strongSelf.contentSize = layout.contentSize + strongSelf.insets = layout.insets + } + }) + } + } +} diff --git a/submodules/ChatListUI/Sources/Node/ChatListItem.swift b/submodules/ChatListUI/Sources/Node/ChatListItem.swift index 1b4ef38ca5..33615bf3cb 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift @@ -37,6 +37,7 @@ public class ChatListItem: ListViewItem, ChatListSearchItemNeighbour { let presentationData: ChatListPresentationData let context: AccountContext let peerGroupId: PeerGroupId + let isInFilter: Bool let index: ChatListIndex public let content: ChatListItemContent let editing: Bool @@ -58,9 +59,10 @@ public class ChatListItem: ListViewItem, ChatListSearchItemNeighbour { return self.index.pinningIndex != nil } - public init(presentationData: ChatListPresentationData, context: AccountContext, peerGroupId: PeerGroupId, index: ChatListIndex, content: ChatListItemContent, editing: Bool, hasActiveRevealControls: Bool, selected: Bool, header: ListViewItemHeader?, enableContextActions: Bool, hiddenOffset: Bool, interaction: ChatListNodeInteraction) { + public init(presentationData: ChatListPresentationData, context: AccountContext, peerGroupId: PeerGroupId, isInFilter: Bool, index: ChatListIndex, content: ChatListItemContent, editing: Bool, hasActiveRevealControls: Bool, selected: Bool, header: ListViewItemHeader?, enableContextActions: Bool, hiddenOffset: Bool, interaction: ChatListNodeInteraction) { self.presentationData = presentationData self.peerGroupId = peerGroupId + self.isInFilter = isInFilter self.context = context self.index = index self.content = content @@ -202,7 +204,7 @@ private func canArchivePeer(id: PeerId, accountPeerId: PeerId) -> Bool { return true } -private func revealOptions(strings: PresentationStrings, theme: PresentationTheme, isPinned: Bool, isMuted: Bool?, groupId: PeerGroupId, peerId: PeerId, accountPeerId: PeerId, canDelete: Bool, isEditing: Bool) -> [ItemListRevealOption] { +private func revealOptions(strings: PresentationStrings, theme: PresentationTheme, isPinned: Bool, isMuted: Bool?, groupId: PeerGroupId, peerId: PeerId, accountPeerId: PeerId, canDelete: Bool, isEditing: Bool, isInFilter: Bool) -> [ItemListRevealOption] { var options: [ItemListRevealOption] = [] if !isEditing { if case .group = groupId { @@ -224,7 +226,7 @@ private func revealOptions(strings: PresentationStrings, theme: PresentationThem if canDelete { options.append(ItemListRevealOption(key: RevealOptionKey.delete.rawValue, title: strings.Common_Delete, icon: deleteIcon, color: theme.list.itemDisclosureActions.destructive.fillColor, textColor: theme.list.itemDisclosureActions.destructive.foregroundColor)) } - if !isEditing { + if !isEditing && !isInFilter { if case .root = groupId { if canArchivePeer(id: peerId, accountPeerId: accountPeerId) { options.append(ItemListRevealOption(key: RevealOptionKey.archive.rawValue, title: strings.ChatList_ArchiveAction, icon: archiveIcon, color: theme.list.itemDisclosureActions.inactive.fillColor, textColor: theme.list.itemDisclosureActions.inactive.foregroundColor)) @@ -248,7 +250,7 @@ private func groupReferenceRevealOptions(strings: PresentationStrings, theme: Pr return options } -private func leftRevealOptions(strings: PresentationStrings, theme: PresentationTheme, isUnread: Bool, isEditing: Bool, isPinned: Bool, isSavedMessages: Bool, groupId: PeerGroupId) -> [ItemListRevealOption] { +private func leftRevealOptions(strings: PresentationStrings, theme: PresentationTheme, isUnread: Bool, isEditing: Bool, isPinned: Bool, isSavedMessages: Bool, groupId: PeerGroupId, isInFilter: Bool) -> [ItemListRevealOption] { if case .group = groupId { return [] } @@ -258,7 +260,7 @@ private func leftRevealOptions(strings: PresentationStrings, theme: Presentation } else { options.append(ItemListRevealOption(key: RevealOptionKey.toggleMarkedUnread.rawValue, title: strings.DialogList_Unread, icon: unreadIcon, color: theme.list.itemDisclosureActions.accent.fillColor, textColor: theme.list.itemDisclosureActions.accent.foregroundColor)) } - if !isEditing { + if !isEditing && !isInFilter { if isPinned { options.append(ItemListRevealOption(key: RevealOptionKey.unpin.rawValue, title: strings.DialogList_Unpin, icon: unpinIcon, color: theme.list.itemDisclosureActions.constructive.fillColor, textColor: theme.list.itemDisclosureActions.constructive.foregroundColor)) } else { @@ -1170,9 +1172,9 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { let isPinned = item.index.pinningIndex != nil if item.enableContextActions && !isAd { - peerRevealOptions = revealOptions(strings: item.presentationData.strings, theme: item.presentationData.theme, isPinned: isPinned, isMuted: item.context.account.peerId != item.index.messageIndex.id.peerId ? (currentMutedIconImage != nil) : nil, groupId: item.peerGroupId, peerId: renderedPeer.peerId, accountPeerId: item.context.account.peerId, canDelete: true, isEditing: item.editing) + peerRevealOptions = revealOptions(strings: item.presentationData.strings, theme: item.presentationData.theme, isPinned: isPinned, isMuted: item.context.account.peerId != item.index.messageIndex.id.peerId ? (currentMutedIconImage != nil) : nil, groupId: item.peerGroupId, peerId: renderedPeer.peerId, accountPeerId: item.context.account.peerId, canDelete: true, isEditing: item.editing, isInFilter: item.isInFilter) if case let .chat(itemPeer) = contentPeer { - peerLeftRevealOptions = leftRevealOptions(strings: item.presentationData.strings, theme: item.presentationData.theme, isUnread: unreadCount.unread, isEditing: item.editing, isPinned: isPinned, isSavedMessages: itemPeer.peerId == item.context.account.peerId, groupId: item.peerGroupId) + peerLeftRevealOptions = leftRevealOptions(strings: item.presentationData.strings, theme: item.presentationData.theme, isUnread: unreadCount.unread, isEditing: item.editing, isPinned: isPinned, isSavedMessages: itemPeer.peerId == item.context.account.peerId, groupId: item.peerGroupId, isInFilter: item.isInFilter) } else { peerLeftRevealOptions = [] } diff --git a/submodules/ChatListUI/Sources/Node/ChatListNode.swift b/submodules/ChatListUI/Sources/Node/ChatListNode.swift index f3aaa459bd..6d2f4b5903 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNode.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNode.swift @@ -61,11 +61,12 @@ public final class ChatListNodeInteraction { let togglePeerMarkedUnread: (PeerId, Bool) -> Void let toggleArchivedFolderHiddenByDefault: () -> Void let activateChatPreview: (ChatListItem, ASDisplayNode, ContextGesture?) -> Void + let present: (ViewController) -> Void public var searchTextHighightState: String? var highlightedChatLocation: ChatListHighlightedLocation? - public init(activateSearch: @escaping () -> Void, peerSelected: @escaping (Peer) -> Void, disabledPeerSelected: @escaping (Peer) -> Void, togglePeerSelected: @escaping (PeerId) -> Void, messageSelected: @escaping (Peer, Message, Bool) -> Void, groupSelected: @escaping (PeerGroupId) -> Void, addContact: @escaping (String) -> Void, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, setItemPinned: @escaping (PinnedItemId, Bool) -> Void, setPeerMuted: @escaping (PeerId, Bool) -> Void, deletePeer: @escaping (PeerId) -> Void, updatePeerGrouping: @escaping (PeerId, Bool) -> Void, togglePeerMarkedUnread: @escaping (PeerId, Bool) -> Void, toggleArchivedFolderHiddenByDefault: @escaping () -> Void, activateChatPreview: @escaping (ChatListItem, ASDisplayNode, ContextGesture?) -> Void) { + public init(activateSearch: @escaping () -> Void, peerSelected: @escaping (Peer) -> Void, disabledPeerSelected: @escaping (Peer) -> Void, togglePeerSelected: @escaping (PeerId) -> Void, messageSelected: @escaping (Peer, Message, Bool) -> Void, groupSelected: @escaping (PeerGroupId) -> Void, addContact: @escaping (String) -> Void, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, setItemPinned: @escaping (PinnedItemId, Bool) -> Void, setPeerMuted: @escaping (PeerId, Bool) -> Void, deletePeer: @escaping (PeerId) -> Void, updatePeerGrouping: @escaping (PeerId, Bool) -> Void, togglePeerMarkedUnread: @escaping (PeerId, Bool) -> Void, toggleArchivedFolderHiddenByDefault: @escaping () -> Void, activateChatPreview: @escaping (ChatListItem, ASDisplayNode, ContextGesture?) -> Void, present: @escaping (ViewController) -> Void) { self.activateSearch = activateSearch self.peerSelected = peerSelected self.disabledPeerSelected = disabledPeerSelected @@ -81,6 +82,7 @@ public final class ChatListNodeInteraction { self.togglePeerMarkedUnread = togglePeerMarkedUnread self.toggleArchivedFolderHiddenByDefault = toggleArchivedFolderHiddenByDefault self.activateChatPreview = activateChatPreview + self.present = present } } @@ -142,13 +144,15 @@ public struct ChatListNodeState: Equatable { } } -private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatListNodeInteraction, peerGroupId: PeerGroupId, mode: ChatListNodeMode, entries: [ChatListNodeViewTransitionInsertEntry]) -> [ListViewInsertItem] { +private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatListNodeInteraction, peerGroupId: PeerGroupId, isInFilter: Bool, mode: ChatListNodeMode, entries: [ChatListNodeViewTransitionInsertEntry]) -> [ListViewInsertItem] { return entries.map { entry -> ListViewInsertItem in switch entry.entry { + case .HeaderEntry: + return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListEmptyHeaderItem(), directionHint: entry.directionHint) case let .PeerEntry(index, presentationData, message, combinedReadState, notificationSettings, embeddedState, peer, presence, summaryInfo, editing, hasActiveRevealControls, selected, inputActivities, isAd, hasFailedMessages): switch mode { case .chatList: - return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListItem(presentationData: presentationData, context: context, peerGroupId: peerGroupId, index: index, content: .peer(message: message, peer: peer, combinedReadState: combinedReadState, notificationSettings: notificationSettings, presence: presence, summaryInfo: summaryInfo, embeddedState: embeddedState, inputActivities: inputActivities, isAd: isAd, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: hasFailedMessages), editing: editing, hasActiveRevealControls: hasActiveRevealControls, selected: selected, header: nil, enableContextActions: true, hiddenOffset: false, interaction: nodeInteraction), directionHint: entry.directionHint) + return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListItem(presentationData: presentationData, context: context, peerGroupId: peerGroupId, isInFilter: isInFilter, index: index, content: .peer(message: message, peer: peer, combinedReadState: combinedReadState, notificationSettings: notificationSettings, presence: presence, summaryInfo: summaryInfo, embeddedState: embeddedState, inputActivities: inputActivities, isAd: isAd, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: hasFailedMessages), editing: editing, hasActiveRevealControls: hasActiveRevealControls, selected: selected, header: nil, enableContextActions: true, hiddenOffset: false, interaction: nodeInteraction), directionHint: entry.directionHint) case let .peers(filter): let itemPeer = peer.chatMainPeer var chatPeer: Peer? @@ -227,20 +231,20 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL case let .HoleEntry(_, theme): return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListHoleItem(theme: theme), directionHint: entry.directionHint) case let .GroupReferenceEntry(index, presentationData, groupId, peers, message, editing, unreadState, revealed, hiddenByDefault): - return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListItem(presentationData: presentationData, context: context, peerGroupId: peerGroupId, index: index, content: .groupReference(groupId: groupId, peers: peers, message: message, unreadState: unreadState, hiddenByDefault: hiddenByDefault), editing: editing, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: true, hiddenOffset: hiddenByDefault && !revealed, interaction: nodeInteraction), directionHint: entry.directionHint) + return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListItem(presentationData: presentationData, context: context, peerGroupId: peerGroupId, isInFilter: isInFilter, index: index, content: .groupReference(groupId: groupId, peers: peers, message: message, unreadState: unreadState, hiddenByDefault: hiddenByDefault), editing: editing, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: true, hiddenOffset: hiddenByDefault && !revealed, interaction: nodeInteraction), directionHint: entry.directionHint) case let .ArchiveIntro(presentationData): return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListArchiveInfoItem(theme: presentationData.theme, strings: presentationData.strings), directionHint: entry.directionHint) } } } -private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatListNodeInteraction, peerGroupId: PeerGroupId, mode: ChatListNodeMode, entries: [ChatListNodeViewTransitionUpdateEntry]) -> [ListViewUpdateItem] { +private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatListNodeInteraction, peerGroupId: PeerGroupId, isInFilter: Bool, mode: ChatListNodeMode, entries: [ChatListNodeViewTransitionUpdateEntry]) -> [ListViewUpdateItem] { return entries.map { entry -> ListViewUpdateItem in switch entry.entry { case let .PeerEntry(index, presentationData, message, combinedReadState, notificationSettings, embeddedState, peer, presence, summaryInfo, editing, hasActiveRevealControls, selected, inputActivities, isAd, hasFailedMessages): switch mode { case .chatList: - return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListItem(presentationData: presentationData, context: context, peerGroupId: peerGroupId, index: index, content: .peer(message: message, peer: peer, combinedReadState: combinedReadState, notificationSettings: notificationSettings, presence: presence, summaryInfo: summaryInfo, embeddedState: embeddedState, inputActivities: inputActivities, isAd: isAd, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: hasFailedMessages), editing: editing, hasActiveRevealControls: hasActiveRevealControls, selected: selected, header: nil, enableContextActions: true, hiddenOffset: false, interaction: nodeInteraction), directionHint: entry.directionHint) + return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListItem(presentationData: presentationData, context: context, peerGroupId: peerGroupId, isInFilter: isInFilter, index: index, content: .peer(message: message, peer: peer, combinedReadState: combinedReadState, notificationSettings: notificationSettings, presence: presence, summaryInfo: summaryInfo, embeddedState: embeddedState, inputActivities: inputActivities, isAd: isAd, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: hasFailedMessages), editing: editing, hasActiveRevealControls: hasActiveRevealControls, selected: selected, header: nil, enableContextActions: true, hiddenOffset: false, interaction: nodeInteraction), directionHint: entry.directionHint) case let .peers(filter): let itemPeer = peer.chatMainPeer var chatPeer: Peer? @@ -275,15 +279,17 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL case let .HoleEntry(_, theme): return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListHoleItem(theme: theme), directionHint: entry.directionHint) case let .GroupReferenceEntry(index, presentationData, groupId, peers, message, editing, unreadState, revealed, hiddenByDefault): - return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListItem(presentationData: presentationData, context: context, peerGroupId: peerGroupId, index: index, content: .groupReference(groupId: groupId, peers: peers, message: message, unreadState: unreadState, hiddenByDefault: hiddenByDefault), editing: editing, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: true, hiddenOffset: hiddenByDefault && !revealed, interaction: nodeInteraction), directionHint: entry.directionHint) + return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListItem(presentationData: presentationData, context: context, peerGroupId: peerGroupId, isInFilter: isInFilter, index: index, content: .groupReference(groupId: groupId, peers: peers, message: message, unreadState: unreadState, hiddenByDefault: hiddenByDefault), editing: editing, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: true, hiddenOffset: hiddenByDefault && !revealed, interaction: nodeInteraction), directionHint: entry.directionHint) case let .ArchiveIntro(presentationData): return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListArchiveInfoItem(theme: presentationData.theme, strings: presentationData.strings), directionHint: entry.directionHint) + case .HeaderEntry: + return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListEmptyHeaderItem(), directionHint: entry.directionHint) } } } -private func mappedChatListNodeViewListTransition(context: AccountContext, nodeInteraction: ChatListNodeInteraction, peerGroupId: PeerGroupId, mode: ChatListNodeMode, transition: ChatListNodeViewTransition) -> ChatListNodeListViewTransition { - return ChatListNodeListViewTransition(chatListView: transition.chatListView, deleteItems: transition.deleteItems, insertItems: mappedInsertEntries(context: context, nodeInteraction: nodeInteraction, peerGroupId: peerGroupId, mode: mode, entries: transition.insertEntries), updateItems: mappedUpdateEntries(context: context, nodeInteraction: nodeInteraction, peerGroupId: peerGroupId, mode: mode, entries: transition.updateEntries), options: transition.options, scrollToItem: transition.scrollToItem, stationaryItemRange: transition.stationaryItemRange, adjustScrollToFirstItem: transition.adjustScrollToFirstItem) +private func mappedChatListNodeViewListTransition(context: AccountContext, nodeInteraction: ChatListNodeInteraction, peerGroupId: PeerGroupId, isInFilter: Bool, mode: ChatListNodeMode, transition: ChatListNodeViewTransition) -> ChatListNodeListViewTransition { + return ChatListNodeListViewTransition(chatListView: transition.chatListView, deleteItems: transition.deleteItems, insertItems: mappedInsertEntries(context: context, nodeInteraction: nodeInteraction, peerGroupId: peerGroupId, isInFilter: isInFilter, mode: mode, entries: transition.insertEntries), updateItems: mappedUpdateEntries(context: context, nodeInteraction: nodeInteraction, peerGroupId: peerGroupId, isInFilter: isInFilter, mode: mode, entries: transition.updateEntries), options: transition.options, scrollToItem: transition.scrollToItem, stationaryItemRange: transition.stationaryItemRange, adjustScrollToFirstItem: transition.adjustScrollToFirstItem) } private final class ChatListOpaqueTransactionState { @@ -323,6 +329,11 @@ public enum ChatListNodeEmptyState: Equatable { case empty(isLoading: Bool) } +enum ChatListNodePaneSwitchAnimationDirection { + case left + case right +} + public final class ChatListNode: ListView { private let controlsHistoryPreload: Bool private let context: AccountContext @@ -349,6 +360,7 @@ public final class ChatListNode: ListView { public var deletePeerChat: ((PeerId) -> Void)? public var updatePeerGrouping: ((PeerId, Bool) -> Void)? public var presentAlert: ((String) -> Void)? + public var present: ((ViewController) -> Void)? public var toggleArchivedFolderHiddenByDefault: (() -> Void)? public var activateChatPreview: ((ChatListItem, ASDisplayNode, ContextGesture?) -> Void)? @@ -367,8 +379,9 @@ public final class ChatListNode: ListView { return self.statePromise.get() } + var paneSwitchAnimation: (ChatListNodePaneSwitchAnimationDirection, ContainedViewLayoutTransition)? private var currentLocation: ChatListNodeLocation? - var chatListFilter: ChatListFilter? { + private(set) var chatListFilter: ChatListFilter? { didSet { self.chatListFilterValue.set(.single(self.chatListFilter)) @@ -379,10 +392,17 @@ public final class ChatListNode: ListView { } } } + private let updatedFilterDisposable = MetaDisposable() private let chatListFilterValue = Promise() var chatListFilterSignal: Signal { return self.chatListFilterValue.get() } + private var hasUpdatedAppliedChatListFilterValueOnce = false + private var currentAppliedChatListFilterValue: ChatListFilter? + private let appliedChatListFilterValue = Promise() + var appliedChatListFilterSignal: Signal { + return self.appliedChatListFilterValue.get() + } private let chatListLocation = ValuePromise() private let chatListDisposable = MetaDisposable() private var activityStatusesDisposable: Disposable? @@ -413,7 +433,7 @@ public final class ChatListNode: ListView { } } - public var isEmptyUpdated: ((ChatListNodeEmptyState) -> Void)? + var isEmptyUpdated: ((ChatListNodeEmptyState, Bool, ChatListNodePaneSwitchAnimationDirection?, ContainedViewLayoutTransition) -> Void)? private var currentIsEmptyState: ChatListNodeEmptyState? public var addedVisibleChatsWithPeerIds: (([PeerId]) -> Void)? @@ -545,6 +565,8 @@ public final class ChatListNode: ListView { } else { gesture?.cancel() } + }, present: { [weak self] c in + self?.present?(c) }) let viewProcessingQueue = self.viewProcessingQueue @@ -782,8 +804,10 @@ public final class ChatListNode: ListView { updatedScrollPosition = nil } + let isInFilter = filter != nil + return preparedChatListNodeViewTransition(from: previousView, to: processedView, reason: reason, previewing: previewing, disableAnimations: disableAnimations, account: context.account, scrollPosition: updatedScrollPosition, searchMode: searchMode) - |> map({ mappedChatListNodeViewListTransition(context: context, nodeInteraction: nodeInteraction, peerGroupId: groupId, mode: mode, transition: $0) }) + |> map({ mappedChatListNodeViewListTransition(context: context, nodeInteraction: nodeInteraction, peerGroupId: groupId, isInFilter: isInFilter, mode: mode, transition: $0) }) |> runOn(prepareOnMainQueue ? Queue.mainQueue() : viewProcessingQueue) } @@ -1138,11 +1162,50 @@ public final class ChatListNode: ListView { } } } + + self.resetFilter() } deinit { self.chatListDisposable.dispose() self.activityStatusesDisposable?.dispose() + self.updatedFilterDisposable.dispose() + } + + func updateFilter(_ filter: ChatListFilter?) { + if filter?.id != self.chatListFilter?.id { + self.chatListFilter = filter + self.resetFilter() + } + } + + private func resetFilter() { + if let chatListFilter = chatListFilter { + let preferencesKey: PostboxViewKey = .preferences(keys: Set([PreferencesKeys.chatListFilters])) + self.updatedFilterDisposable.set((context.account.postbox.combinedView(keys: [preferencesKey]) + |> map { view -> ChatListFilter? in + guard let preferencesView = view.views[preferencesKey] as? PreferencesView else { + return nil + } + let filersState = preferencesView.values[PreferencesKeys.chatListFilters] as? ChatListFiltersState ?? ChatListFiltersState.default + for filter in filersState.filters { + if filter.id == chatListFilter.id { + return filter + } + } + return nil + } + |> deliverOnMainQueue).start(next: { [weak self] updatedFilter in + guard let strongSelf = self else { + return + } + if strongSelf.chatListFilter != updatedFilter { + strongSelf.chatListFilter = updatedFilter + } + })) + } else { + self.updatedFilterDisposable.set(nil) + } } public func updateThemeAndStrings(theme: PresentationTheme, fontSize: PresentationFontSize, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, nameSortOrder: PresentationPersonNameOrder, nameDisplayOrder: PresentationPersonNameOrder, disableAnimations: Bool) { @@ -1200,6 +1263,13 @@ public final class ChatListNode: ListView { if let (transition, completion) = self.enqueuedTransition { self.enqueuedTransition = nil + let paneSwitchCopyView: UIView? + if let (direction, transition) = self.paneSwitchAnimation, let copyView = self.view.snapshotContentTree(unhide: false, keepTransform: true) { + paneSwitchCopyView = copyView + } else { + paneSwitchCopyView = nil + } + let completion: (ListViewDisplayedItemRange) -> Void = { [weak self] visibleRange in if let strongSelf = self { strongSelf.chatListView = transition.chatListView @@ -1241,9 +1311,16 @@ public final class ChatListNode: ListView { if transition.chatListView.filteredEntries.isEmpty { isEmpty = true } else { - if transition.chatListView.filteredEntries.count == 1 { - if case .GroupReferenceEntry = transition.chatListView.filteredEntries[0] { - isEmpty = true + if transition.chatListView.filteredEntries.count <= 2 { + isEmpty = true + loop: for entry in transition.chatListView.filteredEntries { + switch entry { + case .GroupReferenceEntry, .HeaderEntry: + break + default: + isEmpty = false + break loop + } } } } @@ -1260,16 +1337,12 @@ public final class ChatListNode: ListView { case .GroupReferenceEntry, .HoleEntry, .PeerEntry: containsChats = true break loop - case .ArchiveIntro: + case .ArchiveIntro, .HeaderEntry: break } } isEmptyState = .notEmpty(containsChats: containsChats) } - if strongSelf.currentIsEmptyState != isEmptyState { - strongSelf.currentIsEmptyState = isEmptyState - strongSelf.isEmptyUpdated?(isEmptyState) - } var insertedPeerIds: [PeerId] = [] for item in transition.insertItems { @@ -1286,6 +1359,40 @@ public final class ChatListNode: ListView { strongSelf.addedVisibleChatsWithPeerIds?(insertedPeerIds) } + var isEmptyUpdate: (ChatListNodePaneSwitchAnimationDirection?, ContainedViewLayoutTransition) = (nil, .immediate) + if let (direction, transition) = strongSelf.paneSwitchAnimation { + strongSelf.paneSwitchAnimation = nil + if let copyView = paneSwitchCopyView { + let offset: CGFloat + switch direction { + case .left: + offset = -strongSelf.bounds.width + case .right: + offset = strongSelf.bounds.width + } + copyView.frame = strongSelf.bounds.offsetBy(dx: offset, dy: 0.0) + strongSelf.view.addSubview(copyView) + transition.animateHorizontalOffsetAdditive(node: strongSelf, offset: offset, completion: { [weak copyView] in + copyView?.removeFromSuperview() + }) + isEmptyUpdate = (direction, transition) + } + } else { + if transition.options.contains(.AnimateInsertion) { + isEmptyUpdate.1 = .animated(duration: 0.25, curve: .easeInOut) + } + } + + if strongSelf.currentIsEmptyState != isEmptyState { + strongSelf.currentIsEmptyState = isEmptyState + strongSelf.isEmptyUpdated?(isEmptyState, transition.chatListView.filter != nil, isEmptyUpdate.0, isEmptyUpdate.1) + } + + if !strongSelf.hasUpdatedAppliedChatListFilterValueOnce || transition.chatListView.filter != strongSelf.currentAppliedChatListFilterValue { + strongSelf.currentAppliedChatListFilterValue = transition.chatListView.filter + strongSelf.appliedChatListFilterValue.set(.single(transition.chatListView.filter)) + } + completion() } } diff --git a/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift b/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift index 7798787502..2da2b52acf 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift @@ -6,6 +6,7 @@ import TelegramPresentationData import MergeLists enum ChatListNodeEntryId: Hashable { + case Header case Hole(Int64) case PeerId(Int64) case GroupId(PeerGroupId) @@ -13,6 +14,7 @@ enum ChatListNodeEntryId: Hashable { } enum ChatListNodeEntry: Comparable, Identifiable { + case HeaderEntry case PeerEntry(index: ChatListIndex, presentationData: ChatListPresentationData, message: Message?, readState: CombinedPeerReadState?, notificationSettings: PeerNotificationSettings?, embeddedInterfaceState: PeerChatListEmbeddedInterfaceState?, peer: RenderedPeer, presence: PeerPresence?, summaryInfo: ChatListMessageTagSummaryInfo, editing: Bool, hasActiveRevealControls: Bool, selected: Bool, inputActivities: [(Peer, PeerInputActivity)]?, isAd: Bool, hasFailedMessages: Bool) case HoleEntry(ChatListHole, theme: PresentationTheme) case GroupReferenceEntry(index: ChatListIndex, presentationData: ChatListPresentationData, groupId: PeerGroupId, peers: [ChatListGroupReferencePeer], message: Message?, editing: Bool, unreadState: PeerGroupUnreadCountersCombinedSummary, revealed: Bool, hiddenByDefault: Bool) @@ -20,27 +22,31 @@ enum ChatListNodeEntry: Comparable, Identifiable { var sortIndex: ChatListIndex { switch self { - case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _): - return index - case let .HoleEntry(hole, _): - return ChatListIndex(pinningIndex: nil, messageIndex: hole.index) - case let .GroupReferenceEntry(index, _, _, _, _, _, _, _, _): - return index - case .ArchiveIntro: - return ChatListIndex.absoluteUpperBound + case .HeaderEntry: + return ChatListIndex.absoluteUpperBound + case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + return index + case let .HoleEntry(hole, _): + return ChatListIndex(pinningIndex: nil, messageIndex: hole.index) + case let .GroupReferenceEntry(index, _, _, _, _, _, _, _, _): + return index + case .ArchiveIntro: + return ChatListIndex.absoluteUpperBound.successor } } var stableId: ChatListNodeEntryId { switch self { - case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _): - return .PeerId(index.messageIndex.id.peerId.toInt64()) - case let .HoleEntry(hole, _): - return .Hole(Int64(hole.index.id.id)) - case let .GroupReferenceEntry(_, _, groupId, _, _, _, _, _, _): - return .GroupId(groupId) - case .ArchiveIntro: - return .ArchiveIntro + case .HeaderEntry: + return .Header + case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + return .PeerId(index.messageIndex.id.peerId.toInt64()) + case let .HoleEntry(hole, _): + return .Hole(Int64(hole.index.id.id)) + case let .GroupReferenceEntry(_, _, groupId, _, _, _, _, _, _): + return .GroupId(groupId) + case .ArchiveIntro: + return .ArchiveIntro } } @@ -50,6 +56,12 @@ enum ChatListNodeEntry: Comparable, Identifiable { static func ==(lhs: ChatListNodeEntry, rhs: ChatListNodeEntry) -> Bool { switch lhs { + case .HeaderEntry: + if case .HeaderEntry = rhs { + return true + } else { + return false + } case let .PeerEntry(lhsIndex, lhsPresentationData, lhsMessage, lhsUnreadCount, lhsNotificationSettings, lhsEmbeddedState, lhsPeer, lhsPresence, lhsSummaryInfo, lhsEditing, lhsHasRevealControls, lhsSelected, lhsInputActivities, lhsAd, lhsHasFailedMessages): switch rhs { case let .PeerEntry(rhsIndex, rhsPresentationData, rhsMessage, rhsUnreadCount, rhsNotificationSettings, rhsEmbeddedState, rhsPeer, rhsPresence, rhsSummaryInfo, rhsEditing, rhsHasRevealControls, rhsSelected, rhsInputActivities, rhsAd, rhsHasFailedMessages): @@ -273,6 +285,8 @@ func chatListNodeEntriesForView(_ view: ChatListView, state: ChatListNodeState, if displayArchiveIntro { result.append(.ArchiveIntro(presentationData: state.presentationData)) } + + result.append(.HeaderEntry) } } diff --git a/submodules/ChatListUI/Sources/Node/ChatListNodeLocation.swift b/submodules/ChatListUI/Sources/Node/ChatListNodeLocation.swift index 5878d7df08..802010adb0 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNodeLocation.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNodeLocation.swift @@ -29,79 +29,75 @@ struct ChatListNodeViewUpdate { let scrollPosition: ChatListNodeViewScrollPosition? } -func chatListViewForLocation(groupId: PeerGroupId, location: ChatListNodeLocation, account: Account) -> Signal { - let filterPredicate: ((Peer, PeerNotificationSettings?, Bool) -> Bool)? - if let filter = location.filter { - let includePeers = Set(filter.includePeers) - filterPredicate = { peer, notificationSettings, isUnread in - if includePeers.contains(peer.id) { - return true +func chatListFilterPredicate(filter: ChatListFilter) -> ChatListFilterPredicate { + let includePeers = Set(filter.includePeers) + return ChatListFilterPredicate(includePeerIds: includePeers, include: { peer, notificationSettings, isUnread in + if filter.excludeRead { + if !isUnread { + return false } - if filter.excludeRead { - if !isUnread { - return false - } - } - if filter.excludeMuted { - if let notificationSettings = notificationSettings as? TelegramPeerNotificationSettings { - if case .muted = notificationSettings.muteState { - return false - } - } else { - return false - } - } - if !filter.categories.contains(.privateChats) { - if let user = peer as? TelegramUser { - if user.botInfo == nil { - return false - } - } - } - if !filter.categories.contains(.secretChats) { - if let _ = peer as? TelegramSecretChat { - return false - } - } - if !filter.categories.contains(.bots) { - if let user = peer as? TelegramUser { - if user.botInfo != nil { - return false - } - } - } - if !filter.categories.contains(.privateGroups) { - if let _ = peer as? TelegramGroup { - return false - } else if let channel = peer as? TelegramChannel { - if case .group = channel.info { - if channel.username == nil { - return false - } - } - } - } - if !filter.categories.contains(.publicGroups) { - if let channel = peer as? TelegramChannel { - if case .group = channel.info { - if channel.username != nil { - return false - } - } - } - } - if !filter.categories.contains(.channels) { - if let channel = peer as? TelegramChannel { - if case .broadcast = channel.info { - return false - } - } - } - return true } - } else { - filterPredicate = nil - } + if filter.excludeMuted { + if let notificationSettings = notificationSettings as? TelegramPeerNotificationSettings { + if case .muted = notificationSettings.muteState { + return false + } + } else { + return false + } + } + if !filter.categories.contains(.privateChats) { + if let user = peer as? TelegramUser { + if user.botInfo == nil { + return false + } + } + } + if !filter.categories.contains(.secretChats) { + if let _ = peer as? TelegramSecretChat { + return false + } + } + if !filter.categories.contains(.bots) { + if let user = peer as? TelegramUser { + if user.botInfo != nil { + return false + } + } + } + if !filter.categories.contains(.privateGroups) { + if let _ = peer as? TelegramGroup { + return false + } else if let channel = peer as? TelegramChannel { + if case .group = channel.info { + if channel.username == nil { + return false + } + } + } + } + if !filter.categories.contains(.publicGroups) { + if let channel = peer as? TelegramChannel { + if case .group = channel.info { + if channel.username != nil { + return false + } + } + } + } + if !filter.categories.contains(.channels) { + if let channel = peer as? TelegramChannel { + if case .broadcast = channel.info { + return false + } + } + } + return true + }) +} + +func chatListViewForLocation(groupId: PeerGroupId, location: ChatListNodeLocation, account: Account) -> Signal { + let filterPredicate: ChatListFilterPredicate? = location.filter.flatMap(chatListFilterPredicate) switch location { case let .initial(count, _): diff --git a/submodules/ChatListUI/Sources/TabBarChatListFilterController.swift b/submodules/ChatListUI/Sources/TabBarChatListFilterController.swift index 0845aa6539..c02d9656b6 100644 --- a/submodules/ChatListUI/Sources/TabBarChatListFilterController.swift +++ b/submodules/ChatListUI/Sources/TabBarChatListFilterController.swift @@ -304,7 +304,7 @@ private final class FilterItemNode: ASDisplayNode, AbstractTabBarChatListFilterI } } -func chatListFilterItems(context: AccountContext) -> Signal<[(ChatListFilter, Int)], NoError> { +func chatListFilterItems(context: AccountContext) -> Signal<(Int, [(ChatListFilter, Int)]), NoError> { let preferencesKey: PostboxViewKey = .preferences(keys: [PreferencesKeys.chatListFilters]) return context.account.postbox.combinedView(keys: [preferencesKey]) |> map { combinedView -> [ChatListFilter] in @@ -315,7 +315,7 @@ func chatListFilterItems(context: AccountContext) -> Signal<[(ChatListFilter, In } } |> distinctUntilChanged - |> mapToSignal { filters -> Signal<[(ChatListFilter, Int)], NoError> in + |> mapToSignal { filters -> Signal<(Int, [(ChatListFilter, Int)]), NoError> in var unreadCountItems: [UnreadMessageCountsItem] = [] unreadCountItems.append(.total(nil)) var additionalPeerIds = Set() @@ -334,10 +334,25 @@ func chatListFilterItems(context: AccountContext) -> Signal<[(ChatListFilter, In keys.append(.basicPeer(peerId)) } - return context.account.postbox.combinedView(keys: keys) - |> map { view -> [(ChatListFilter, Int)] in + return combineLatest(queue: context.account.postbox.queue, + context.account.postbox.combinedView(keys: keys), + context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.inAppNotificationSettings]) + ) + |> map { view, sharedData -> (Int, [(ChatListFilter, Int)]) in guard let unreadCounts = view.views[unreadKey] as? UnreadMessageCountsView else { - return [] + return (0, []) + } + + let inAppSettings: InAppNotificationSettings + if let value = sharedData.entries[ApplicationSpecificSharedDataKeys.inAppNotificationSettings] as? InAppNotificationSettings { + inAppSettings = value + } else { + inAppSettings = .defaultSettings + } + let type: RenderedTotalUnreadCountType + switch inAppSettings.totalUnreadCountDisplayStyle { + case .filtered: + type = .filtered } var result: [(ChatListFilter, Int)] = [] @@ -367,11 +382,9 @@ func chatListFilterItems(context: AccountContext) -> Signal<[(ChatListFilter, In } } - var totalUnreadChatCount = 0 + var totalBadge = 0 if let totalState = totalState { - for (_, counters) in totalState.filteredCounters { - totalUnreadChatCount += Int(counters.chatCount) - } + totalBadge = Int(totalState.count(for: inAppSettings.totalUnreadCountDisplayStyle.category, in: inAppSettings.totalUnreadCountDisplayCategory.statsType, with: inAppSettings.totalUnreadCountIncludeTags)) } var shouldUpdateLayout = false @@ -416,7 +429,7 @@ func chatListFilterItems(context: AccountContext) -> Signal<[(ChatListFilter, In result.append((filter, count)) } - return result + return (totalBadge, result) } } } diff --git a/submodules/ContactListUI/Sources/ContactListNode.swift b/submodules/ContactListUI/Sources/ContactListNode.swift index 30be08fba4..0ed5454a31 100644 --- a/submodules/ContactListUI/Sources/ContactListNode.swift +++ b/submodules/ContactListUI/Sources/ContactListNode.swift @@ -191,11 +191,19 @@ private enum ContactListNodeEntry: Comparable, Identifiable { status = .presence(presence, dateTimeFormat) } else if let group = peer as? TelegramGroup { status = .custom(strings.Conversation_StatusMembers(Int32(group.participantCount))) - } else if let _ = peer as? TelegramChannel { - if let participantCount = participantCount, participantCount != 0 { - status = .custom(strings.Conversation_StatusMembers(participantCount)) + } else if let channel = peer as? TelegramChannel { + if case .group = channel.info { + if let participantCount = participantCount, participantCount != 0 { + status = .custom(strings.Conversation_StatusMembers(participantCount)) + } else { + status = .custom(strings.Group_Status) + } } else { - status = .custom(strings.Group_Status) + if let participantCount = participantCount, participantCount != 0 { + status = .custom(strings.Conversation_StatusSubscribers(participantCount)) + } else { + status = .custom(strings.Channel_Status) + } } } else { status = .none @@ -666,7 +674,7 @@ private struct ContactsListNodeTransition { public enum ContactListPresentation { case orderedByPresence(options: [ContactListAdditionalOption]) case natural(options: [ContactListAdditionalOption], includeChatList: Bool) - case search(signal: Signal, searchChatList: Bool, searchDeviceContacts: Bool, searchGroups: Bool) + case search(signal: Signal, searchChatList: Bool, searchDeviceContacts: Bool, searchGroups: Bool, searchChannels: Bool) public var sortOrder: ContactsSortOrder? { switch self { @@ -909,7 +917,7 @@ public final class ContactListNode: ASDisplayNode { includeChatList = natural.includeChatList } - if case let .search(query, searchChatList, searchDeviceContacts, searchGroups) = presentation { + if case let .search(query, searchChatList, searchDeviceContacts, searchGroups, searchChannels) = presentation { return query |> mapToSignal { query in let foundLocalContacts: Signal<([FoundPeer], [PeerId: PeerPresence]), NoError> @@ -919,13 +927,15 @@ public final class ContactListNode: ASDisplayNode { |> mapToSignal { peers -> Signal<([FoundPeer], [PeerId: PeerPresence]), NoError> in var resultPeers: [FoundPeer] = [] for peer in peers { - if searchGroups { + if searchGroups || searchChannels { let mainPeer = peer.chatMainPeer if let _ = mainPeer as? TelegramUser { } else if let _ = mainPeer as? TelegramGroup { } else if let channel = mainPeer as? TelegramChannel { if case .broadcast = channel.info { - continue + if !searchChannels { + continue + } } } else { continue @@ -1010,14 +1020,14 @@ public final class ContactListNode: ASDisplayNode { let matches: Bool if peer.peer is TelegramUser { matches = true - } else if searchGroups { - if peer.peer is TelegramGroup { + } else if searchGroups || searchChannels { + if peer.peer is TelegramGroup && searchGroups { matches = true } else if let channel = peer.peer as? TelegramChannel { if case .group = channel.info { - matches = true + matches = searchGroups } else { - matches = false + matches = searchChannels } } else { matches = false @@ -1040,14 +1050,14 @@ public final class ContactListNode: ASDisplayNode { let matches: Bool if peer.peer is TelegramUser { matches = true - } else if searchGroups { + } else if searchGroups || searchChannels { if peer.peer is TelegramGroup { - matches = true + matches = searchGroups } else if let channel = peer.peer as? TelegramChannel { if case .group = channel.info { - matches = true + matches = searchGroups } else { - matches = false + matches = searchChannels } } else { matches = false diff --git a/submodules/ContextUI/Sources/ContextActionNode.swift b/submodules/ContextUI/Sources/ContextActionNode.swift index dece341b12..1b7af31951 100644 --- a/submodules/ContextUI/Sources/ContextActionNode.swift +++ b/submodules/ContextUI/Sources/ContextActionNode.swift @@ -2,6 +2,7 @@ import Foundation import AsyncDisplayKit import Display import TelegramPresentationData +import SwiftSignalKit enum ContextActionSibling { case none @@ -19,8 +20,12 @@ final class ContextActionNode: ASDisplayNode { private let textNode: ImmediateTextNode private let statusNode: ImmediateTextNode? private let iconNode: ASImageNode + private let badgeBackgroundNode: ASImageNode + private let badgeTextNode: ImmediateTextNode private let buttonNode: HighlightTrackingButtonNode + private var iconDisposable: Disposable? + init(presentationData: PresentationData, action: ContextMenuActionItem, getController: @escaping () -> ContextController?, actionSelected: @escaping (ContextMenuActionResult) -> Void) { self.action = action self.getController = getController @@ -72,7 +77,22 @@ final class ContextActionNode: ASDisplayNode { self.iconNode.displaysAsynchronously = false self.iconNode.displayWithoutProcessing = true self.iconNode.isUserInteractionEnabled = false - self.iconNode.image = action.icon(presentationData.theme) + if action.iconSource == nil { + self.iconNode.image = action.icon(presentationData.theme) + } + + self.badgeBackgroundNode = ASImageNode() + self.badgeBackgroundNode.image = generateStretchableFilledCircleImage(diameter: 18.0, color: presentationData.theme.contextMenu.badgeFillColor) + self.badgeBackgroundNode.isAccessibilityElement = false + self.badgeBackgroundNode.displaysAsynchronously = false + self.badgeBackgroundNode.displayWithoutProcessing = true + self.badgeBackgroundNode.isUserInteractionEnabled = false + + self.badgeTextNode = ImmediateTextNode() + self.badgeTextNode.attributedText = NSAttributedString(string: action.badge, font: Font.regular(14.0), textColor: presentationData.theme.contextMenu.badgeForegroundColor) + self.badgeTextNode.isAccessibilityElement = false + self.badgeTextNode.isUserInteractionEnabled = false + self.badgeTextNode.displaysAsynchronously = false self.buttonNode = HighlightTrackingButtonNode() self.buttonNode.isAccessibilityElement = true @@ -85,6 +105,8 @@ final class ContextActionNode: ASDisplayNode { self.addSubnode(self.textNode) self.statusNode.flatMap(self.addSubnode) self.addSubnode(self.iconNode) + self.addSubnode(self.badgeBackgroundNode) + self.addSubnode(self.badgeTextNode) self.addSubnode(self.buttonNode) self.buttonNode.highligthedChanged = { [weak self] highligted in @@ -99,6 +121,20 @@ final class ContextActionNode: ASDisplayNode { } } self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside) + + if let iconSource = action.iconSource { + self.iconDisposable = (iconSource.signal + |> deliverOnMainQueue).start(next: { [weak self] image in + guard let strongSelf = self else { + return + } + strongSelf.iconNode.image = image + }) + } + } + + deinit { + self.iconDisposable?.dispose() } func updateLayout(constrainedWidth: CGFloat, previous: ContextActionSibling, next: ContextActionSibling) -> (CGSize, (CGSize, ContainedViewLayoutTransition) -> Void) { @@ -106,7 +142,12 @@ final class ContextActionNode: ASDisplayNode { let iconSideInset: CGFloat = 12.0 let verticalInset: CGFloat = 12.0 - let iconSize = self.iconNode.image.flatMap({ $0.size }) ?? CGSize() + let iconSize: CGSize + if let iconSource = self.action.iconSource { + iconSize = iconSource.size + } else { + iconSize = self.iconNode.image.flatMap({ $0.size }) ?? CGSize() + } let standardIconWidth: CGFloat = 32.0 var rightTextInset: CGFloat = sideInset @@ -114,17 +155,36 @@ final class ContextActionNode: ASDisplayNode { rightTextInset = max(iconSize.width, standardIconWidth) + iconSideInset + sideInset } - let textSize = self.textNode.updateLayout(CGSize(width: constrainedWidth - sideInset - rightTextInset, height: .greatestFiniteMagnitude)) - let statusSize = self.statusNode?.updateLayout(CGSize(width: constrainedWidth - sideInset - rightTextInset, height: .greatestFiniteMagnitude)) ?? CGSize() + let badgeTextSize = self.badgeTextNode.updateLayout(CGSize(width: constrainedWidth, height: .greatestFiniteMagnitude)) + let badgeInset: CGFloat = 4.0 + + let badgeSize: CGSize + let badgeWidthSpace: CGFloat + let badgeSpacing: CGFloat = 10.0 + if badgeTextSize.width.isZero { + badgeSize = CGSize() + badgeWidthSpace = 0.0 + } else { + badgeSize = CGSize(width: max(18.0, badgeTextSize.width + badgeInset * 2.0), height: 18.0) + badgeWidthSpace = badgeSize.width + badgeSpacing + } + + let textSize = self.textNode.updateLayout(CGSize(width: constrainedWidth - sideInset - rightTextInset - badgeWidthSpace, height: .greatestFiniteMagnitude)) + let statusSize = self.statusNode?.updateLayout(CGSize(width: constrainedWidth - sideInset - rightTextInset - badgeWidthSpace, height: .greatestFiniteMagnitude)) ?? CGSize() if !statusSize.width.isZero, let statusNode = self.statusNode { let verticalSpacing: CGFloat = 2.0 let combinedTextHeight = textSize.height + verticalSpacing + statusSize.height return (CGSize(width: max(textSize.width, statusSize.width) + sideInset + rightTextInset, height: verticalInset * 2.0 + combinedTextHeight), { size, transition in let verticalOrigin = floor((size.height - combinedTextHeight) / 2.0) - transition.updateFrameAdditive(node: self.textNode, frame: CGRect(origin: CGPoint(x: sideInset, y: verticalOrigin), size: textSize)) + let textFrame = CGRect(origin: CGPoint(x: sideInset, y: verticalOrigin), size: textSize) + transition.updateFrameAdditive(node: self.textNode, frame: textFrame) transition.updateFrameAdditive(node: statusNode, frame: CGRect(origin: CGPoint(x: sideInset, y: verticalOrigin + verticalSpacing + textSize.height), size: textSize)) + let badgeFrame = CGRect(origin: CGPoint(x: textFrame.maxX + badgeSpacing, y: floor((size.height - badgeSize.height) / 2.0)), size: badgeSize) + transition.updateFrame(node: self.badgeBackgroundNode, frame: badgeFrame) + transition.updateFrame(node: self.badgeTextNode, frame: CGRect(origin: CGPoint(x: badgeFrame.minX + floorToScreenPixels((badgeFrame.width - badgeTextSize.width) / 2.0), y: badgeFrame.minY + floor((badgeFrame.height - badgeTextSize.height) / 2.0)), size: badgeTextSize)) + if !iconSize.width.isZero { transition.updateFrameAdditive(node: self.iconNode, frame: CGRect(origin: CGPoint(x: size.width - standardIconWidth - iconSideInset + floor((standardIconWidth - iconSize.width) / 2.0), y: floor((size.height - iconSize.height) / 2.0)), size: iconSize)) } @@ -136,12 +196,17 @@ final class ContextActionNode: ASDisplayNode { } else { return (CGSize(width: textSize.width + sideInset + rightTextInset, height: verticalInset * 2.0 + textSize.height), { size, transition in let verticalOrigin = floor((size.height - textSize.height) / 2.0) - transition.updateFrameAdditive(node: self.textNode, frame: CGRect(origin: CGPoint(x: sideInset, y: verticalOrigin), size: textSize)) + let textFrame = CGRect(origin: CGPoint(x: sideInset, y: verticalOrigin), size: textSize) + transition.updateFrameAdditive(node: self.textNode, frame: textFrame) if !iconSize.width.isZero { transition.updateFrameAdditive(node: self.iconNode, frame: CGRect(origin: CGPoint(x: size.width - standardIconWidth - iconSideInset + floor((standardIconWidth - iconSize.width) / 2.0), y: floor((size.height - iconSize.height) / 2.0)), size: iconSize)) } + let badgeFrame = CGRect(origin: CGPoint(x: textFrame.maxX + badgeSpacing, y: floor((size.height - badgeSize.height) / 2.0)), size: badgeSize) + transition.updateFrame(node: self.badgeBackgroundNode, frame: badgeFrame) + transition.updateFrame(node: self.badgeTextNode, frame: CGRect(origin: CGPoint(x: badgeFrame.minX + floorToScreenPixels((badgeFrame.width - badgeTextSize.width) / 2.0), y: badgeFrame.minY + floor((badgeFrame.height - badgeTextSize.height) / 2.0)), size: badgeTextSize)) + transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height))) transition.updateFrame(node: self.highlightedBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height))) transition.updateFrame(node: self.buttonNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height))) @@ -172,7 +237,12 @@ final class ContextActionNode: ASDisplayNode { break } - self.iconNode.image = self.action.icon(presentationData.theme) + if self.action.iconSource == nil { + self.iconNode.image = self.action.icon(presentationData.theme) + } + + self.badgeBackgroundNode.image = generateStretchableFilledCircleImage(diameter: 18.0, color: presentationData.theme.contextMenu.badgeFillColor) + self.badgeTextNode.attributedText = NSAttributedString(string: self.badgeTextNode.attributedText?.string ?? "", font: Font.regular(14.0), textColor: presentationData.theme.contextMenu.badgeForegroundColor) } @objc private func buttonPressed() { diff --git a/submodules/ContextUI/Sources/ContextActionsContainerNode.swift b/submodules/ContextUI/Sources/ContextActionsContainerNode.swift index 50cb4d90a5..d049efe4f7 100644 --- a/submodules/ContextUI/Sources/ContextActionsContainerNode.swift +++ b/submodules/ContextUI/Sources/ContextActionsContainerNode.swift @@ -49,6 +49,23 @@ private final class InnerActionsContainerNode: ASDisplayNode { private(set) var gesture: UIGestureRecognizer? private var currentHighlightedActionNode: ContextActionNode? + var panSelectionGestureEnabled: Bool = true { + didSet { + if self.panSelectionGestureEnabled != oldValue, let gesture = self.gesture { + gesture.isEnabled = self.panSelectionGestureEnabled + + self.itemNodes.forEach({ itemNode in + switch itemNode { + case let .action(actionNode): + actionNode.isUserInteractionEnabled = !self.panSelectionGestureEnabled + default: + break + } + }) + } + } + } + init(presentationData: PresentationData, items: [ContextMenuItem], getController: @escaping () -> ContextController?, actionSelected: @escaping (ContextMenuActionResult) -> Void, feedbackTap: @escaping () -> Void) { self.presentationData = presentationData self.feedbackTap = feedbackTap @@ -120,6 +137,7 @@ private final class InnerActionsContainerNode: ASDisplayNode { } } self.view.addGestureRecognizer(gesture) + gesture.isEnabled = self.panSelectionGestureEnabled } func updateLayout(widthClass: ContainerViewLayoutSizeClass, constrainedWidth: CGFloat, transition: ContainedViewLayoutTransition) -> CGSize { @@ -375,6 +393,14 @@ final class ContextActionsContainerNode: ASDisplayNode { private let actionsNode: InnerActionsContainerNode private let textSelectionTipNode: InnerTextSelectionTipContainerNode? + var panSelectionGestureEnabled: Bool = true { + didSet { + if self.panSelectionGestureEnabled != oldValue { + self.actionsNode.panSelectionGestureEnabled = self.panSelectionGestureEnabled + } + } + } + init(presentationData: PresentationData, items: [ContextMenuItem], getController: @escaping () -> ContextController?, actionSelected: @escaping (ContextMenuActionResult) -> Void, feedbackTap: @escaping () -> Void, displayTextSelectionTip: Bool) { self.actionsNode = InnerActionsContainerNode(presentationData: presentationData, items: items, getController: getController, actionSelected: actionSelected, feedbackTap: feedbackTap) if displayTextSelectionTip { diff --git a/submodules/ContextUI/Sources/ContextController.swift b/submodules/ContextUI/Sources/ContextController.swift index ef9600e46c..f63658522d 100644 --- a/submodules/ContextUI/Sources/ContextController.swift +++ b/submodules/ContextUI/Sources/ContextController.swift @@ -28,18 +28,32 @@ public enum ContextMenuActionResult { case custom(ContainedViewLayoutTransition) } +public struct ContextMenuActionItemIconSource { + public let size: CGSize + public let signal: Signal + + public init(size: CGSize, signal: Signal) { + self.size = size + self.signal = signal + } +} + public final class ContextMenuActionItem { public let text: String public let textColor: ContextMenuActionItemTextColor public let textLayout: ContextMenuActionItemTextLayout + public let badge: String public let icon: (PresentationTheme) -> UIImage? + public let iconSource: ContextMenuActionItemIconSource? public let action: (ContextController, @escaping (ContextMenuActionResult) -> Void) -> Void - public init(text: String, textColor: ContextMenuActionItemTextColor = .primary, textLayout: ContextMenuActionItemTextLayout = .twoLinesMax, icon: @escaping (PresentationTheme) -> UIImage?, action: @escaping (ContextController, @escaping (ContextMenuActionResult) -> Void) -> Void) { + public init(text: String, textColor: ContextMenuActionItemTextColor = .primary, textLayout: ContextMenuActionItemTextLayout = .twoLinesMax, badge: String = "", icon: @escaping (PresentationTheme) -> UIImage?, iconSource: ContextMenuActionItemIconSource? = nil, action: @escaping (ContextController, @escaping (ContextMenuActionResult) -> Void) -> Void) { self.text = text self.textColor = textColor self.textLayout = textLayout + self.badge = badge self.icon = icon + self.iconSource = iconSource self.action = action } } @@ -192,7 +206,6 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi self.scrollNode.addSubnode(self.dismissNode) self.scrollNode.addSubnode(self.actionsContainerNode) - self.scrollNode.addSubnode(self.contentContainerNode) self.reactionContextNode.flatMap(self.addSubnode) if let recognizer = recognizer { @@ -413,7 +426,12 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi let takenViewInfo = source.takeView() if let takenViewInfo = takenViewInfo, let parentSupernode = takenViewInfo.contentContainingNode.supernode { - self.contentContainerNode.contentNode = .extracted(takenViewInfo.contentContainingNode) + self.contentContainerNode.contentNode = .extracted(node: takenViewInfo.contentContainingNode, keepInPlace: source.keepInPlace) + if source.keepInPlace { + self.clippingNode.addSubnode(self.contentContainerNode) + } else { + self.scrollNode.addSubnode(self.contentContainerNode) + } let contentParentNode = takenViewInfo.contentContainingNode takenViewInfo.contentContainingNode.layoutUpdated = { [weak contentParentNode, weak self] size in guard let strongSelf = self, let contentParentNode = contentParentNode, let parentSupernode = contentParentNode.supernode else { @@ -462,6 +480,7 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi self?.attemptTransitionControllerIntoNavigation() }) self.contentContainerNode.contentNode = .controller(contentParentNode) + self.scrollNode.addSubnode(self.contentContainerNode) self.contentContainerNode.clipsToBounds = true self.contentContainerNode.cornerRadius = 14.0 self.contentContainerNode.addSubnode(contentParentNode) @@ -479,8 +498,8 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi switch self.source { case let .extracted(source): - if let contentAreaInScreenSpace = contentAreaInScreenSpace { - var updatedContentAreaInScreenSpace = contentAreaInScreenSpace + if let contentAreaInScreenSpace = self.contentAreaInScreenSpace, let maybeContentNode = self.contentContainerNode.contentNode, case let .extracted(_, keepInPlace) = maybeContentNode { + var updatedContentAreaInScreenSpace = contentAreaInScreenSpace updatedContentAreaInScreenSpace.origin.x = 0.0 updatedContentAreaInScreenSpace.size.width = self.bounds.width @@ -544,7 +563,7 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi if let contentNode = self.contentContainerNode.contentNode { switch contentNode { - case let .extracted(extracted): + case let .extracted(extracted, keepInPlace): let springDuration: Double = 0.42 * animationDurationFactor let springDamping: CGFloat = 104.0 @@ -555,15 +574,24 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi let contentParentNode = extracted let localSourceFrame = self.view.convert(originalProjectedContentViewFrame.1, to: self.scrollNode.view) + let localContentSourceFrame: CGRect + if keepInPlace { + localContentSourceFrame = self.view.convert(originalProjectedContentViewFrame.1, to: self.contentContainerNode.view.superview) + } else { + localContentSourceFrame = localSourceFrame + } + if let reactionContextNode = self.reactionContextNode { reactionContextNode.animateIn(from: CGRect(origin: CGPoint(x: originalProjectedContentViewFrame.1.minX, y: originalProjectedContentViewFrame.1.minY), size: contentParentNode.contentRect.size)) } self.actionsContainerNode.layer.animateSpring(from: NSValue(cgPoint: CGPoint(x: localSourceFrame.center.x - self.actionsContainerNode.position.x, y: localSourceFrame.center.y - self.actionsContainerNode.position.y)), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: springDuration, initialVelocity: 0.0, damping: springDamping, additive: true) - let contentContainerOffset = CGPoint(x: localSourceFrame.center.x - self.contentContainerNode.frame.center.x - contentParentNode.contentRect.minX, y: localSourceFrame.center.y - self.contentContainerNode.frame.center.y) + let contentContainerOffset = CGPoint(x: localContentSourceFrame.center.x - self.contentContainerNode.frame.center.x - contentParentNode.contentRect.minX, y: localContentSourceFrame.center.y - self.contentContainerNode.frame.center.y) self.contentContainerNode.layer.animateSpring(from: NSValue(cgPoint: contentContainerOffset), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: springDuration, initialVelocity: 0.0, damping: springDamping, additive: true) contentParentNode.applyAbsoluteOffsetSpring?(-contentContainerOffset.y, springDuration, springDamping) } + + extracted.willUpdateIsExtractedToContextPreview?(true, .animated(duration: 0.2, curve: .easeInOut)) case .controller: let springDuration: Double = 0.52 * animationDurationFactor let springDamping: CGFloat = 110.0 @@ -620,7 +648,7 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi switch self.source { case let .extracted(source): - guard let maybeContentNode = self.contentContainerNode.contentNode, case let .extracted(contentParentNode) = maybeContentNode else { + guard let maybeContentNode = self.contentContainerNode.contentNode, case let .extracted(contentParentNode, keepInPlace) = maybeContentNode else { return } @@ -663,8 +691,6 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi self.clippingNode.layer.animateBoundsOriginYAdditive(from: 0.0, to: updatedContentAreaInScreenSpace.minY, duration: transitionDuration * animationDurationFactor, timingFunction: transitionCurve.timingFunction, removeOnCompletion: false) } - contentParentNode.willUpdateIsExtractedToContextPreview?(false) - let intermediateCompletion: () -> Void = { [weak contentParentNode] in if completedEffect && completedContentNode && completedActionsNode { switch result { @@ -737,8 +763,15 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi if animateOutToItem, let originalProjectedContentViewFrame = self.originalProjectedContentViewFrame { let localSourceFrame = self.view.convert(originalProjectedContentViewFrame.1, to: self.scrollNode.view) + let localContentSourceFrame: CGRect + if keepInPlace { + localContentSourceFrame = self.view.convert(originalProjectedContentViewFrame.1, to: self.contentContainerNode.view.superview) + } else { + localContentSourceFrame = localSourceFrame + } + self.actionsContainerNode.layer.animatePosition(from: CGPoint(), to: CGPoint(x: localSourceFrame.center.x - self.actionsContainerNode.position.x, y: localSourceFrame.center.y - self.actionsContainerNode.position.y), duration: transitionDuration * animationDurationFactor, timingFunction: transitionCurve.timingFunction, removeOnCompletion: false, additive: true) - let contentContainerOffset = CGPoint(x: localSourceFrame.center.x - self.contentContainerNode.frame.center.x - contentParentNode.contentRect.minX, y: localSourceFrame.center.y - self.contentContainerNode.frame.center.y - contentParentNode.contentRect.minY) + let contentContainerOffset = CGPoint(x: localContentSourceFrame.center.x - self.contentContainerNode.frame.center.x - contentParentNode.contentRect.minX, y: localContentSourceFrame.center.y - self.contentContainerNode.frame.center.y - contentParentNode.contentRect.minY) self.contentContainerNode.layer.animatePosition(from: CGPoint(), to: contentContainerOffset, duration: transitionDuration * animationDurationFactor, timingFunction: transitionCurve.timingFunction, removeOnCompletion: false, additive: true, completion: { _ in completedContentNode = true intermediateCompletion() @@ -749,6 +782,8 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi if let reactionContextNode = self.reactionContextNode { reactionContextNode.animateOut(to: CGRect(origin: CGPoint(x: originalProjectedContentViewFrame.1.minX, y: originalProjectedContentViewFrame.1.minY), size: contentParentNode.contentRect.size), animatingOutToReaction: self.reactionContextNodeIsAnimatingOut) } + + contentParentNode.willUpdateIsExtractedToContextPreview?(false, .animated(duration: 0.2, curve: .easeInOut)) } else { if let snapshotView = contentParentNode.contentNode.view.snapshotContentTree() { self.contentContainerNode.view.addSubview(snapshotView) @@ -763,7 +798,9 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi completedContentNode = true intermediateCompletion() }) - //self.contentContainerNode.layer.animateScale(from: 1.0, to: 0.1, duration: transitionDuration * animationDurationFactor, removeOnCompletion: false) + + contentParentNode.contentNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + contentParentNode.willUpdateIsExtractedToContextPreview?(false, .animated(duration: 0.2, curve: .easeInOut)) if let reactionContextNode = self.reactionContextNode { reactionContextNode.animateOut(to: nil, animatingOutToReaction: self.reactionContextNodeIsAnimatingOut) @@ -1054,8 +1091,8 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi if let contentNode = self.contentContainerNode.contentNode { switch contentNode { - case let .extracted(contentParentNode): - let contentActionsSpacing: CGFloat = 8.0 + case let .extracted(contentParentNode, keepInPlace): + let contentActionsSpacing: CGFloat = keepInPlace ? 16.0 : 8.0 if let originalProjectedContentViewFrame = self.originalProjectedContentViewFrame { let isInitialLayout = self.actionsContainerNode.frame.size.width.isZero let previousContainerFrame = self.view.convert(self.contentContainerNode.frame, from: self.scrollNode.view) @@ -1065,8 +1102,24 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi self.contentContainerNode.updateLayout(size: contentSize, scaledSize: contentSize, transition: transition) let maximumActionsFrameOrigin = max(60.0, layout.size.height - layout.intrinsicInsets.bottom - actionsBottomInset - actionsSize.height) - var originalActionsFrame = CGRect(origin: CGPoint(x: max(actionsSideInset, min(layout.size.width - actionsSize.width - actionsSideInset, originalProjectedContentViewFrame.1.minX)), y: min(originalProjectedContentViewFrame.1.maxY + contentActionsSpacing, maximumActionsFrameOrigin)), size: actionsSize) - var originalContentFrame = CGRect(origin: CGPoint(x: originalProjectedContentViewFrame.1.minX, y: originalActionsFrame.minY - contentActionsSpacing - originalProjectedContentViewFrame.1.size.height), size: originalProjectedContentViewFrame.1.size) + let preferredActionsX: CGFloat + let originalActionsY: CGFloat + if keepInPlace { + originalActionsY = originalProjectedContentViewFrame.1.minY - contentActionsSpacing - actionsSize.height + preferredActionsX = originalProjectedContentViewFrame.1.maxX - actionsSize.width + } else { + originalActionsY = min(originalProjectedContentViewFrame.1.maxY + contentActionsSpacing, maximumActionsFrameOrigin) + preferredActionsX = originalProjectedContentViewFrame.1.minX + } + var originalActionsFrame = CGRect(origin: CGPoint(x: max(actionsSideInset, min(layout.size.width - actionsSize.width - actionsSideInset, preferredActionsX)), y: originalActionsY), size: actionsSize) + var originalContentX: CGFloat = originalProjectedContentViewFrame.1.minX + let originalContentY: CGFloat + if keepInPlace { + originalContentY = originalProjectedContentViewFrame.1.minY + } else { + originalContentY = originalActionsFrame.minY - contentActionsSpacing - originalProjectedContentViewFrame.1.size.height + } + var originalContentFrame = CGRect(origin: CGPoint(x: originalContentX, y: originalContentY), size: originalProjectedContentViewFrame.1.size) let topEdge = max(contentTopInset, self.contentAreaInScreenSpace?.minY ?? 0.0) if originalContentFrame.minY < topEdge { let requiredOffset = topEdge - originalContentFrame.minY @@ -1076,21 +1129,43 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi originalContentFrame = originalContentFrame.offsetBy(dx: 0.0, dy: offset) } - let contentHeight = max(layout.size.height, max(layout.size.height, originalActionsFrame.maxY + actionsBottomInset) - originalContentFrame.minY + contentTopInset) + var contentHeight: CGFloat + if keepInPlace { + contentHeight = max(layout.size.height, max(layout.size.height, originalActionsFrame.maxY + actionsBottomInset) - originalActionsFrame.minY + contentTopInset) + } else { + contentHeight = max(layout.size.height, max(layout.size.height, originalActionsFrame.maxY + actionsBottomInset) - originalContentFrame.minY + contentTopInset) + } + + var overflowOffset: CGFloat + let contentContainerFrame: CGRect + if keepInPlace { + overflowOffset = min(0.0, originalActionsFrame.minY - contentTopInset) + contentContainerFrame = originalContentFrame.offsetBy(dx: -contentParentNode.contentRect.minX, dy: -contentParentNode.contentRect.minY) + if keepInPlace && !overflowOffset.isZero { + let offsetDelta = contentParentNode.contentRect.height + 4.0 + overflowOffset += offsetDelta + originalActionsFrame.origin.x -= contentParentNode.contentRect.maxX - contentParentNode.contentRect.minX + 14.0 + originalActionsFrame.origin.y += offsetDelta + contentHeight -= offsetDelta + } + } else { + overflowOffset = min(0.0, originalContentFrame.minY - contentTopInset) + contentContainerFrame = originalContentFrame.offsetBy(dx: -contentParentNode.contentRect.minX, dy: -overflowOffset - contentParentNode.contentRect.minY) + } let scrollContentSize = CGSize(width: layout.size.width, height: contentHeight) if self.scrollNode.view.contentSize != scrollContentSize { self.scrollNode.view.contentSize = scrollContentSize } + self.actionsContainerNode.panSelectionGestureEnabled = scrollContentSize.height <= layout.size.height - let overflowOffset = min(0.0, originalContentFrame.minY - contentTopInset) - - let contentContainerFrame = originalContentFrame.offsetBy(dx: -contentParentNode.contentRect.minX, dy: -overflowOffset - contentParentNode.contentRect.minY) transition.updateFrame(node: self.contentContainerNode, frame: contentContainerFrame) actionsContainerTransition.updateFrame(node: self.actionsContainerNode, frame: originalActionsFrame.offsetBy(dx: 0.0, dy: -overflowOffset)) if isInitialLayout { - self.scrollNode.view.contentOffset = CGPoint(x: 0.0, y: -overflowOffset) + if !keepInPlace { + self.scrollNode.view.contentOffset = CGPoint(x: 0.0, y: -overflowOffset) + } let currentContainerFrame = self.view.convert(self.contentContainerNode.frame, from: self.scrollNode.view) if overflowOffset < 0.0 { transition.animateOffsetAdditive(node: self.scrollNode, offset: currentContainerFrame.minY - previousContainerFrame.minY) @@ -1213,6 +1288,7 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi if self.scrollNode.view.contentSize != scrollContentSize { self.scrollNode.view.contentSize = scrollContentSize } + self.actionsContainerNode.panSelectionGestureEnabled = true let overflowOffset = min(0.0, originalContentFrame.minY - contentTopInset) @@ -1262,9 +1338,15 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi guard let layout = self.validLayout else { return } - if let maybeContentNode = self.contentContainerNode.contentNode, case let .extracted(contentParentNode) = maybeContentNode { + if let maybeContentNode = self.contentContainerNode.contentNode, case let .extracted(contentParentNode, keepInPlace) = maybeContentNode { let contentContainerFrame = self.contentContainerNode.frame - contentParentNode.updateAbsoluteRect?(contentContainerFrame.offsetBy(dx: 0.0, dy: -self.scrollNode.view.contentOffset.y), layout.size) + let absoluteRect: CGRect + if keepInPlace { + absoluteRect = contentContainerFrame + } else { + absoluteRect = contentContainerFrame.offsetBy(dx: 0.0, dy: -self.scrollNode.view.contentOffset.y) + } + contentParentNode.updateAbsoluteRect?(absoluteRect, layout.size) } } @@ -1280,7 +1362,7 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi let mappedPoint = self.view.convert(point, to: self.scrollNode.view) if let maybeContentNode = self.contentContainerNode.contentNode { switch maybeContentNode { - case let .extracted(contentParentNode): + case let .extracted(contentParentNode, _): let contentPoint = self.view.convert(point, to: contentParentNode.contentNode.view) if let result = contentParentNode.contentNode.hitTest(contentPoint, with: event) { if result is TextSelectionNodeView { @@ -1346,6 +1428,8 @@ public final class ContextControllerPutBackViewInfo { } public protocol ContextExtractedContentSource: class { + var keepInPlace: Bool { get } + func takeView() -> ContextControllerTakeViewInfo? func putBack() -> ContextControllerPutBackViewInfo? } diff --git a/submodules/Display/Source/ContainedViewLayoutTransition.swift b/submodules/Display/Source/ContainedViewLayoutTransition.swift index 7635ad88ba..14f4bbf4bf 100644 --- a/submodules/Display/Source/ContainedViewLayoutTransition.swift +++ b/submodules/Display/Source/ContainedViewLayoutTransition.swift @@ -328,12 +328,14 @@ public extension ContainedViewLayoutTransition { } } - func animateHorizontalOffsetAdditive(node: ASDisplayNode, offset: CGFloat) { + func animateHorizontalOffsetAdditive(node: ASDisplayNode, offset: CGFloat, completion: (() -> Void)? = nil) { switch self { case .immediate: break case let .animated(duration, curve): - node.layer.animateBoundsOriginXAdditive(from: offset, to: 0.0, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction) + node.layer.animateBoundsOriginXAdditive(from: offset, to: 0.0, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, completion: { _ in + completion?() + }) } } diff --git a/submodules/ContextUI/Sources/ContextContentContainerNode.swift b/submodules/Display/Source/ContextContentContainerNode.swift similarity index 81% rename from submodules/ContextUI/Sources/ContextContentContainerNode.swift rename to submodules/Display/Source/ContextContentContainerNode.swift index ec77f46adb..add4037501 100644 --- a/submodules/ContextUI/Sources/ContextContentContainerNode.swift +++ b/submodules/Display/Source/ContextContentContainerNode.swift @@ -2,14 +2,14 @@ import Foundation import AsyncDisplayKit import Display -final class ContextContentContainerNode: ASDisplayNode { - var contentNode: ContextContentNode? +public final class ContextContentContainerNode: ASDisplayNode { + public var contentNode: ContextContentNode? - override init() { + override public init() { super.init() } - func updateLayout(size: CGSize, scaledSize: CGSize, transition: ContainedViewLayoutTransition) { + public func updateLayout(size: CGSize, scaledSize: CGSize, transition: ContainedViewLayoutTransition) { guard let contentNode = self.contentNode else { return } diff --git a/submodules/ContextUI/Sources/ContextContentSourceNode.swift b/submodules/Display/Source/ContextContentSourceNode.swift similarity index 74% rename from submodules/ContextUI/Sources/ContextContentSourceNode.swift rename to submodules/Display/Source/ContextContentSourceNode.swift index a66ce3cf0a..7b2204cedc 100644 --- a/submodules/ContextUI/Sources/ContextContentSourceNode.swift +++ b/submodules/Display/Source/ContextContentSourceNode.swift @@ -6,7 +6,7 @@ public final class ContextExtractedContentContainingNode: ASDisplayNode { public let contentNode: ContextExtractedContentNode public var contentRect: CGRect = CGRect() public var isExtractedToContextPreview: Bool = false - public var willUpdateIsExtractedToContextPreview: ((Bool) -> Void)? + public var willUpdateIsExtractedToContextPreview: ((Bool, ContainedViewLayoutTransition) -> Void)? public var isExtractedToContextPreviewUpdated: ((Bool) -> Void)? public var updateAbsoluteRect: ((CGRect, CGSize) -> Void)? public var applyAbsoluteOffset: ((CGFloat, ContainedViewLayoutTransitionCurve, Double) -> Void)? @@ -26,12 +26,12 @@ public final class ContextExtractedContentContainingNode: ASDisplayNode { public final class ContextExtractedContentNode: ASDisplayNode { } -final class ContextControllerContentNode: ASDisplayNode { - let sourceNode: ASDisplayNode - let controller: ViewController +public final class ContextControllerContentNode: ASDisplayNode { + public let sourceNode: ASDisplayNode + public let controller: ViewController private let tapped: () -> Void - init(sourceNode: ASDisplayNode, controller: ViewController, tapped: @escaping () -> Void) { + public init(sourceNode: ASDisplayNode, controller: ViewController, tapped: @escaping () -> Void) { self.sourceNode = sourceNode self.controller = controller self.tapped = tapped @@ -41,7 +41,7 @@ final class ContextControllerContentNode: ASDisplayNode { self.addSubnode(controller.displayNode) } - override func didLoad() { + override public func didLoad() { super.didLoad() self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:)))) @@ -53,12 +53,12 @@ final class ContextControllerContentNode: ASDisplayNode { } } - func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) { + public func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) { transition.updateFrame(node: self.controller.displayNode, frame: CGRect(origin: CGPoint(), size: size)) } } -enum ContextContentNode { - case extracted(ContextExtractedContentContainingNode) +public enum ContextContentNode { + case extracted(node: ContextExtractedContentContainingNode, keepInPlace: Bool) case controller(ContextControllerContentNode) } diff --git a/submodules/ContextUI/Sources/ContextControllerSourceNode.swift b/submodules/Display/Source/ContextControllerSourceNode.swift similarity index 86% rename from submodules/ContextUI/Sources/ContextControllerSourceNode.swift rename to submodules/Display/Source/ContextControllerSourceNode.swift index e375015ece..a9219fa485 100644 --- a/submodules/ContextUI/Sources/ContextControllerSourceNode.swift +++ b/submodules/Display/Source/ContextControllerSourceNode.swift @@ -13,6 +13,7 @@ public final class ContextControllerSourceNode: ASDisplayNode { public var activated: ((ContextGesture) -> Void)? public var shouldBegin: ((CGPoint) -> Bool)? public var customActivationProgress: ((CGFloat, ContextGestureTransition) -> Void)? + public var targetNodeForActivationProgress: ASDisplayNode? public func cancelGesture() { self.contextGesture?.cancel() @@ -41,17 +42,19 @@ public final class ContextControllerSourceNode: ASDisplayNode { if let customActivationProgress = strongSelf.customActivationProgress { customActivationProgress(progress, update) } else { + let targetNode = strongSelf.targetNodeForActivationProgress ?? strongSelf + let minScale: CGFloat = (strongSelf.bounds.width - 10.0) / strongSelf.bounds.width let currentScale = 1.0 * (1.0 - progress) + minScale * progress switch update { case .update: - strongSelf.layer.sublayerTransform = CATransform3DMakeScale(currentScale, currentScale, 1.0) + targetNode.layer.sublayerTransform = CATransform3DMakeScale(currentScale, currentScale, 1.0) case .begin: - strongSelf.layer.sublayerTransform = CATransform3DMakeScale(currentScale, currentScale, 1.0) + targetNode.layer.sublayerTransform = CATransform3DMakeScale(currentScale, currentScale, 1.0) case let .ended(previousProgress): let previousScale = 1.0 * (1.0 - previousProgress) + minScale * previousProgress - strongSelf.layer.sublayerTransform = CATransform3DMakeScale(currentScale, currentScale, 1.0) - strongSelf.layer.animateSpring(from: previousScale as NSNumber, to: currentScale as NSNumber, keyPath: "sublayerTransform.scale", duration: 0.5, delay: 0.0, initialVelocity: 0.0, damping: 90.0) + targetNode.layer.sublayerTransform = CATransform3DMakeScale(currentScale, currentScale, 1.0) + targetNode.layer.animateSpring(from: previousScale as NSNumber, to: currentScale as NSNumber, keyPath: "sublayerTransform.scale", duration: 0.5, delay: 0.0, initialVelocity: 0.0, damping: 90.0) } } } diff --git a/submodules/ContextUI/Sources/ContextGesture.swift b/submodules/Display/Source/ContextGesture.swift similarity index 97% rename from submodules/ContextUI/Sources/ContextGesture.swift rename to submodules/Display/Source/ContextGesture.swift index 29b21b1eab..5a758cc8c8 100644 --- a/submodules/ContextUI/Sources/ContextGesture.swift +++ b/submodules/Display/Source/ContextGesture.swift @@ -208,8 +208,9 @@ public final class ContextGesture: UIGestureRecognizer, UIGestureRecognizerDeleg super.touchesCancelled(touches, with: event) if let touch = touches.first, !self.currentProgress.isZero, self.isValidated { + let previousProgress = self.currentProgress self.currentProgress = 0.0 - self.activationProgress?(0.0, .ended(self.currentProgress)) + self.activationProgress?(0.0, .ended(previousProgress)) } self.delayTimer?.invalidate() @@ -220,8 +221,9 @@ public final class ContextGesture: UIGestureRecognizer, UIGestureRecognizerDeleg public func cancel() { if !self.currentProgress.isZero, self.isValidated { + let previousProgress = self.currentProgress self.currentProgress = 0.0 - self.activationProgress?(0.0, .ended(self.currentProgress)) + self.activationProgress?(0.0, .ended(previousProgress)) self.delayTimer?.invalidate() self.animator?.invalidate() diff --git a/submodules/Display/Source/ListView.swift b/submodules/Display/Source/ListView.swift index 66ae03e4bb..d4a09b6662 100644 --- a/submodules/Display/Source/ListView.swift +++ b/submodules/Display/Source/ListView.swift @@ -355,8 +355,9 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture let trackingRecognizer = UIPanGestureRecognizer(target: self, action: #selector(self.trackingGesture(_:))) trackingRecognizer.delegate = self + trackingRecognizer.cancelsTouchesInView = false self.view.addGestureRecognizer(trackingRecognizer) - + self.view.addGestureRecognizer(ListViewReorderingGestureRecognizer(shouldBegin: { [weak self] point in if let strongSelf = self { if let index = strongSelf.itemIndexAtPoint(point) { diff --git a/submodules/Display/Source/ListViewScroller.swift b/submodules/Display/Source/ListViewScroller.swift index c77b2207cb..5052231153 100644 --- a/submodules/Display/Source/ListViewScroller.swift +++ b/submodules/Display/Source/ListViewScroller.swift @@ -28,6 +28,11 @@ public final class ListViewScroller: UIScrollView, UIGestureRecognizerDelegate { return gestureRecognizer.numberOfTouches < 2 } } + + if let view = gestureRecognizer.view?.hitTest(gestureRecognizer.location(in: gestureRecognizer.view), with: nil) as? UIControl { + return !view.isTracking + } + return true } else { return true diff --git a/submodules/Display/Source/Navigation/NavigationController.swift b/submodules/Display/Source/Navigation/NavigationController.swift index 8964945933..64e1748704 100644 --- a/submodules/Display/Source/Navigation/NavigationController.swift +++ b/submodules/Display/Source/Navigation/NavigationController.swift @@ -775,7 +775,7 @@ open class NavigationController: UINavigationController, ContainableController, effectiveRootModalDismissProgress = topModalIsFlat ? 1.0 : topModalDismissProgress visibleRootModalDismissProgress = effectiveRootModalDismissProgress additionalModalFrameProgress = 0.0 - } else if visibleModalCount == 2 { + } else if visibleModalCount >= 2 { effectiveRootModalDismissProgress = 0.0 visibleRootModalDismissProgress = topModalDismissProgress additionalModalFrameProgress = 1.0 - topModalDismissProgress diff --git a/submodules/Display/Source/NavigationBar.swift b/submodules/Display/Source/NavigationBar.swift index 5b715421e5..43bf27a4eb 100644 --- a/submodules/Display/Source/NavigationBar.swift +++ b/submodules/Display/Source/NavigationBar.swift @@ -103,6 +103,10 @@ enum NavigationPreviousAction: Equatable { } open class NavigationBar: ASDisplayNode { + public static var defaultSecondaryContentHeight: CGFloat { + return 38.0 + } + private var presentationData: NavigationBarPresentationData private var validLayout: (CGSize, CGFloat, CGFloat, CGFloat, CGFloat, Bool)? @@ -690,6 +694,7 @@ open class NavigationBar: ASDisplayNode { self.badgeNode.isUserInteractionEnabled = false self.badgeNode.isHidden = true self.backButtonArrow = ASImageNode() + self.backButtonArrow.displayWithoutProcessing = true self.backButtonArrow.displaysAsynchronously = false self.leftButtonNode = NavigationButtonNode() self.rightButtonNode = NavigationButtonNode() @@ -818,7 +823,7 @@ open class NavigationBar: ASDisplayNode { transition.updateAlpha(node: secondaryContentNode, alpha: appearsHidden ? 0.0 : 1.0) } - let apparentAdditionalHeight: CGFloat = self.secondaryContentNode != nil ? 46.0 : 0.0 + let apparentAdditionalHeight: CGFloat = self.secondaryContentNode != nil ? NavigationBar.defaultSecondaryContentHeight : 0.0 let leftButtonInset: CGFloat = leftInset + 16.0 let backButtonInset: CGFloat = leftInset + 27.0 @@ -836,7 +841,7 @@ open class NavigationBar: ASDisplayNode { contentNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: size.height - expansionHeight - apparentAdditionalHeight), size: CGSize(width: size.width, height: expansionHeight)) if appearsHidden { if self.secondaryContentNode != nil { - contentNodeFrame.origin.y += 46.0 + contentNodeFrame.origin.y += NavigationBar.defaultSecondaryContentHeight } } } @@ -1085,6 +1090,7 @@ open class NavigationBar: ASDisplayNode { let node = ASImageNode() node.image = backArrowImage(color: accentColor) node.frame = self.backButtonArrow.frame + node.displayWithoutProcessing = true node.displaysAsynchronously = false return node } else { @@ -1128,7 +1134,7 @@ open class NavigationBar: ASDisplayNode { } if let _ = self.secondaryContentNode { - result += 46.0 + result += NavigationBar.defaultSecondaryContentHeight } return result diff --git a/submodules/Display/Source/NavigationButtonNode.swift b/submodules/Display/Source/NavigationButtonNode.swift index 35109c82bb..d5f59fedb8 100644 --- a/submodules/Display/Source/NavigationButtonNode.swift +++ b/submodules/Display/Source/NavigationButtonNode.swift @@ -65,6 +65,7 @@ private final class NavigationButtonItemNode: ImmediateTextNode { if let value = value { if self.imageNode == nil { let imageNode = ASImageNode() + imageNode.displayWithoutProcessing = true imageNode.displaysAsynchronously = false self.imageNode = imageNode if false, value.size == CGSize(width: 30.0, height: 30.0) { @@ -279,9 +280,16 @@ private final class NavigationButtonItemNode: ImmediateTextNode { } } - public var isEnabled: Bool = true { - didSet(value) { - self.attributedText = NSAttributedString(string: text, attributes: self.attributesForCurrentState()) + public override var isEnabled: Bool { + get { + return super.isEnabled + } + set(value) { + if self.isEnabled != value { + super.isEnabled = value + + self.attributedText = NSAttributedString(string: text, attributes: self.attributesForCurrentState()) + } } } } diff --git a/submodules/Display/Source/TabBarContollerNode.swift b/submodules/Display/Source/TabBarContollerNode.swift index 69cff6073f..dee3c1f46a 100644 --- a/submodules/Display/Source/TabBarContollerNode.swift +++ b/submodules/Display/Source/TabBarContollerNode.swift @@ -25,10 +25,10 @@ final class TabBarControllerNode: ASDisplayNode { } } - init(theme: TabBarControllerTheme, navigationBar: NavigationBar?, itemSelected: @escaping (Int, Bool, [ASDisplayNode]) -> Void, toolbarActionSelected: @escaping (ToolbarActionOption) -> Void) { + init(theme: TabBarControllerTheme, navigationBar: NavigationBar?, itemSelected: @escaping (Int, Bool, [ASDisplayNode]) -> Void, contextAction: @escaping (Int, ContextExtractedContentContainingNode, ContextGesture) -> Void, toolbarActionSelected: @escaping (ToolbarActionOption) -> Void) { self.theme = theme self.navigationBar = navigationBar - self.tabBarNode = TabBarNode(theme: theme, itemSelected: itemSelected) + self.tabBarNode = TabBarNode(theme: theme, itemSelected: itemSelected, contextAction: contextAction) self.toolbarActionSelected = toolbarActionSelected super.init() diff --git a/submodules/Display/Source/TabBarController.swift b/submodules/Display/Source/TabBarController.swift index f5a28af7b6..bf48fe944b 100644 --- a/submodules/Display/Source/TabBarController.swift +++ b/submodules/Display/Source/TabBarController.swift @@ -14,8 +14,10 @@ public final class TabBarControllerTheme { public let tabBarBadgeBackgroundColor: UIColor public let tabBarBadgeStrokeColor: UIColor public let tabBarBadgeTextColor: UIColor + public let tabBarExtractedIconColor: UIColor + public let tabBarExtractedTextColor: UIColor - public init(backgroundColor: UIColor, tabBarBackgroundColor: UIColor, tabBarSeparatorColor: UIColor, tabBarIconColor: UIColor, tabBarSelectedIconColor: UIColor, tabBarTextColor: UIColor, tabBarSelectedTextColor: UIColor, tabBarBadgeBackgroundColor: UIColor, tabBarBadgeStrokeColor: UIColor, tabBarBadgeTextColor: UIColor) { + public init(backgroundColor: UIColor, tabBarBackgroundColor: UIColor, tabBarSeparatorColor: UIColor, tabBarIconColor: UIColor, tabBarSelectedIconColor: UIColor, tabBarTextColor: UIColor, tabBarSelectedTextColor: UIColor, tabBarBadgeBackgroundColor: UIColor, tabBarBadgeStrokeColor: UIColor, tabBarBadgeTextColor: UIColor, tabBarExtractedIconColor: UIColor, tabBarExtractedTextColor: UIColor) { self.backgroundColor = backgroundColor self.tabBarBackgroundColor = tabBarBackgroundColor self.tabBarSeparatorColor = tabBarSeparatorColor @@ -26,6 +28,8 @@ public final class TabBarControllerTheme { self.tabBarBadgeBackgroundColor = tabBarBadgeBackgroundColor self.tabBarBadgeStrokeColor = tabBarBadgeStrokeColor self.tabBarBadgeTextColor = tabBarBadgeTextColor + self.tabBarExtractedIconColor = tabBarExtractedIconColor + self.tabBarExtractedTextColor = tabBarExtractedTextColor } } @@ -229,6 +233,13 @@ open class TabBarController: ViewController { } })) } + }, contextAction: { [weak self] index, node, gesture in + guard let strongSelf = self else { + return + } + if index >= 0 && index < strongSelf.controllers.count { + strongSelf.controllers[index].tabBarItemContextAction(sourceNode: node, gesture: gesture) + } }, toolbarActionSelected: { [weak self] action in self?.currentController?.toolbarActionSelected(action: action) }) @@ -367,7 +378,7 @@ open class TabBarController: ViewController { } } self.controllers = controllers - self.tabBarControllerNode.tabBarNode.tabBarItems = self.controllers.map({ $0.tabBarItem }) + self.tabBarControllerNode.tabBarNode.tabBarItems = self.controllers.map({ TabBarNodeItem(item: $0.tabBarItem, hasContext: $0.hasTabBarItemContextAction) }) let signals = combineLatest(self.controllers.map({ $0.tabBarItem }).map { tabBarItem -> Signal in if let tabBarItem = tabBarItem, tabBarItem.image == nil { diff --git a/submodules/Display/Source/TabBarNode.swift b/submodules/Display/Source/TabBarNode.swift index 18c40f9989..4e964e2d9b 100644 --- a/submodules/Display/Source/TabBarNode.swift +++ b/submodules/Display/Source/TabBarNode.swift @@ -90,24 +90,63 @@ private func tabBarItemImage(_ image: UIImage?, title: String, backgroundColor: private let badgeFont = Font.regular(13.0) private final class TabBarItemNode: ASDisplayNode { + let extractedContainerNode: ContextExtractedContentContainingNode + let containerNode: ContextControllerSourceNode let imageNode: ASImageNode let textImageNode: ASImageNode + let contextImageNode: ASImageNode + let contextTextImageNode: ASImageNode var contentWidth: CGFloat? override init() { + self.extractedContainerNode = ContextExtractedContentContainingNode() + self.containerNode = ContextControllerSourceNode() + self.imageNode = ASImageNode() + self.imageNode.isUserInteractionEnabled = false + self.imageNode.displayWithoutProcessing = true self.imageNode.displaysAsynchronously = false self.imageNode.isAccessibilityElement = false self.textImageNode = ASImageNode() + self.textImageNode.isUserInteractionEnabled = false + self.textImageNode.displayWithoutProcessing = true self.textImageNode.displaysAsynchronously = false self.textImageNode.isAccessibilityElement = false + self.contextImageNode = ASImageNode() + self.contextImageNode.isUserInteractionEnabled = false + self.contextImageNode.displayWithoutProcessing = true + self.contextImageNode.displaysAsynchronously = false + self.contextImageNode.isAccessibilityElement = false + self.contextImageNode.alpha = 0.0 + self.contextTextImageNode = ASImageNode() + self.contextTextImageNode.isUserInteractionEnabled = false + self.contextTextImageNode.displayWithoutProcessing = true + self.contextTextImageNode.displaysAsynchronously = false + self.contextTextImageNode.isAccessibilityElement = false + self.contextTextImageNode.alpha = 0.0 + super.init() self.isAccessibilityElement = true - self.addSubnode(self.textImageNode) - self.addSubnode(self.imageNode) + self.extractedContainerNode.contentNode.addSubnode(self.textImageNode) + self.extractedContainerNode.contentNode.addSubnode(self.imageNode) + self.extractedContainerNode.contentNode.addSubnode(self.contextTextImageNode) + self.extractedContainerNode.contentNode.addSubnode(self.contextImageNode) + self.containerNode.addSubnode(self.extractedContainerNode) + self.containerNode.targetNodeForActivationProgress = self.extractedContainerNode.contentNode + self.addSubnode(self.containerNode) + + self.extractedContainerNode.willUpdateIsExtractedToContextPreview = { [weak self] isExtracted, transition in + guard let strongSelf = self else { + return + } + transition.updateAlpha(node: strongSelf.imageNode, alpha: isExtracted ? 0.0 : 1.0) + transition.updateAlpha(node: strongSelf.textImageNode, alpha: isExtracted ? 0.0 : 1.0) + transition.updateAlpha(node: strongSelf.contextImageNode, alpha: isExtracted ? 1.0 : 0.0) + transition.updateAlpha(node: strongSelf.contextTextImageNode, alpha: isExtracted ? 1.0 : 0.0) + } } } @@ -135,8 +174,8 @@ private final class TabBarNodeContainer { var selectedImageValue: UIImage? var appliedSelectedImageValue: UIImage? - init(item: UITabBarItem, imageNode: TabBarItemNode, updateBadge: @escaping (String) -> Void, updateTitle: @escaping (String, Bool) -> Void, updateImage: @escaping (UIImage?) -> Void, updateSelectedImage: @escaping (UIImage?) -> Void) { - self.item = item + init(item: TabBarNodeItem, imageNode: TabBarItemNode, updateBadge: @escaping (String) -> Void, updateTitle: @escaping (String, Bool) -> Void, updateImage: @escaping (UIImage?) -> Void, updateSelectedImage: @escaping (UIImage?) -> Void, contextAction: @escaping (ContextExtractedContentContainingNode, ContextGesture) -> Void) { + self.item = item.item self.imageNode = imageNode self.imageNode.isAccessibilityElement = true @@ -148,6 +187,7 @@ private final class TabBarNodeContainer { self.badgeBackgroundNode = ASImageNode() self.badgeBackgroundNode.isUserInteractionEnabled = false + self.badgeBackgroundNode.displayWithoutProcessing = true self.badgeBackgroundNode.displaysAsynchronously = false self.badgeBackgroundNode.isAccessibilityElement = false @@ -160,25 +200,33 @@ private final class TabBarNodeContainer { self.badgeContainerNode.addSubnode(self.badgeBackgroundNode) self.badgeContainerNode.addSubnode(self.badgeTextNode) - self.badgeValue = item.badgeValue ?? "" - self.updateBadgeListenerIndex = UITabBarItem_addSetBadgeListener(item, { value in + self.badgeValue = item.item.badgeValue ?? "" + self.updateBadgeListenerIndex = UITabBarItem_addSetBadgeListener(item.item, { value in updateBadge(value ?? "") }) - self.titleValue = item.title - self.updateTitleListenerIndex = item.addSetTitleListener { value, animated in + self.titleValue = item.item.title + self.updateTitleListenerIndex = item.item.addSetTitleListener { value, animated in updateTitle(value ?? "", animated) } - self.imageValue = item.image - self.updateImageListenerIndex = item.addSetImageListener { value in + self.imageValue = item.item.image + self.updateImageListenerIndex = item.item.addSetImageListener { value in updateImage(value) } - self.selectedImageValue = item.selectedImage - self.updateSelectedImageListenerIndex = item.addSetSelectedImageListener { value in + self.selectedImageValue = item.item.selectedImage + self.updateSelectedImageListenerIndex = item.item.addSetSelectedImageListener { value in updateSelectedImage(value) } + + imageNode.containerNode.activated = { [weak self] gesture in + guard let strongSelf = self else { + return + } + contextAction(strongSelf.imageNode.extractedContainerNode, gesture) + } + imageNode.containerNode.isGestureEnabled = item.hasContext } deinit { @@ -189,8 +237,18 @@ private final class TabBarNodeContainer { } } +final class TabBarNodeItem { + let item: UITabBarItem + let hasContext: Bool + + init(item: UITabBarItem, hasContext: Bool) { + self.item = item + self.hasContext = hasContext + } +} + class TabBarNode: ASDisplayNode { - var tabBarItems: [UITabBarItem] = [] { + var tabBarItems: [TabBarNodeItem] = [] { didSet { self.reloadTabBarItems() } @@ -211,6 +269,7 @@ class TabBarNode: ASDisplayNode { } private let itemSelected: (Int, Bool, [ASDisplayNode]) -> Void + private let contextAction: (Int, ContextExtractedContentContainingNode, ContextGesture) -> Void private var theme: TabBarControllerTheme private var validLayout: (CGSize, CGFloat, CGFloat, CGFloat)? @@ -222,8 +281,11 @@ class TabBarNode: ASDisplayNode { let separatorNode: ASDisplayNode private var tabBarNodeContainers: [TabBarNodeContainer] = [] - init(theme: TabBarControllerTheme, itemSelected: @escaping (Int, Bool, [ASDisplayNode]) -> Void) { + private var tapRecognizer: TapLongTapOrDoubleTapGestureRecognizer? + + init(theme: TabBarControllerTheme, itemSelected: @escaping (Int, Bool, [ASDisplayNode]) -> Void, contextAction: @escaping (Int, ContextExtractedContentContainingNode, ContextGesture) -> Void) { self.itemSelected = itemSelected + self.contextAction = contextAction self.theme = theme self.separatorNode = ASDisplayNode() @@ -246,11 +308,25 @@ class TabBarNode: ASDisplayNode { override func didLoad() { super.didLoad() - self.view.addGestureRecognizer(TabBarTapRecognizer(tap: { [weak self] point in - self?.tapped(at: point, longTap: false) - }, longTap: { [weak self] point in - self?.tapped(at: point, longTap: true) - })) + let recognizer = TapLongTapOrDoubleTapGestureRecognizer(target: self, action: #selector(self.tapLongTapOrDoubleTapGesture(_:))) + recognizer.tapActionAtPoint = { _ in + return .keepWithSingleTap + } + self.tapRecognizer = recognizer + self.view.addGestureRecognizer(recognizer) + } + + @objc private func tapLongTapOrDoubleTapGesture(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) { + switch recognizer.state { + case .ended: + if let (gesture, location) = recognizer.lastRecognizedGestureAndLocation { + if case .tap = gesture { + self.tapped(at: location, longTap: false) + } + } + default: + break + } } func updateTheme(_ theme: TabBarControllerTheme) { @@ -287,7 +363,6 @@ class TabBarNode: ASDisplayNode { private func reloadTabBarItems() { for node in self.tabBarNodeContainers { node.imageNode.removeFromSupernode() - node.badgeContainerNode.removeFromSupernode() } self.centered = self.theme.tabBarTextColor == .clear @@ -296,7 +371,6 @@ class TabBarNode: ASDisplayNode { for i in 0 ..< self.tabBarItems.count { let item = self.tabBarItems[i] let node = TabBarItemNode() - node.isUserInteractionEnabled = false let container = TabBarNodeContainer(item: item, imageNode: node, updateBadge: { [weak self] value in self?.updateNodeBadge(i, value: value) }, updateTitle: { [weak self] _, _ in @@ -305,31 +379,31 @@ class TabBarNode: ASDisplayNode { self?.updateNodeImage(i, layout: true) }, updateSelectedImage: { [weak self] _ in self?.updateNodeImage(i, layout: true) + }, contextAction: { [weak self] node, gesture in + self?.tapRecognizer?.cancel() + self?.contextAction(i, node, gesture) }) if let selectedIndex = self.selectedIndex, selectedIndex == i { - let (textImage, contentWidth) = tabBarItemImage(item.selectedImage, title: item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarSelectedTextColor, horizontal: self.horizontal, imageMode: false, centered: self.centered) - let (image, imageContentWidth) = tabBarItemImage(item.selectedImage, title: item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarSelectedTextColor, horizontal: self.horizontal, imageMode: true, centered: self.centered) + let (textImage, contentWidth) = tabBarItemImage(item.item.selectedImage, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarSelectedTextColor, horizontal: self.horizontal, imageMode: false, centered: self.centered) + let (image, imageContentWidth) = tabBarItemImage(item.item.selectedImage, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarSelectedTextColor, horizontal: self.horizontal, imageMode: true, centered: self.centered) node.textImageNode.image = textImage node.imageNode.image = image - node.accessibilityLabel = item.title + node.accessibilityLabel = item.item.title node.contentWidth = max(contentWidth, imageContentWidth) } else { - let (textImage, contentWidth) = tabBarItemImage(item.image, title: item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarTextColor, horizontal: self.horizontal, imageMode: false, centered: self.centered) - let (image, imageContentWidth) = tabBarItemImage(item.image, title: item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarIconColor, horizontal: self.horizontal, imageMode: true, centered: self.centered) + let (textImage, contentWidth) = tabBarItemImage(item.item.image, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarTextColor, horizontal: self.horizontal, imageMode: false, centered: self.centered) + let (image, imageContentWidth) = tabBarItemImage(item.item.image, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarIconColor, horizontal: self.horizontal, imageMode: true, centered: self.centered) node.textImageNode.image = textImage - node.accessibilityLabel = item.title + node.accessibilityLabel = item.item.title node.imageNode.image = image node.contentWidth = max(contentWidth, imageContentWidth) } container.badgeBackgroundNode.image = self.badgeImage + node.extractedContainerNode.contentNode.addSubnode(container.badgeContainerNode) tabBarNodeContainers.append(container) self.addSubnode(node) } - for container in tabBarNodeContainers { - self.addSubnode(container.badgeContainerNode) - } - self.tabBarNodeContainers = tabBarNodeContainers self.setNeedsLayout() @@ -345,18 +419,26 @@ class TabBarNode: ASDisplayNode { let previousImageSize = node.imageNode.image?.size ?? CGSize() let previousTextImageSize = node.textImageNode.image?.size ?? CGSize() if let selectedIndex = self.selectedIndex, selectedIndex == index { - let (textImage, contentWidth) = tabBarItemImage(item.selectedImage, title: item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarSelectedTextColor, horizontal: self.horizontal, imageMode: false, centered: self.centered) - let (image, imageContentWidth) = tabBarItemImage(item.selectedImage, title: item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarSelectedIconColor, horizontal: self.horizontal, imageMode: true, centered: self.centered) + let (textImage, contentWidth) = tabBarItemImage(item.item.selectedImage, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarSelectedTextColor, horizontal: self.horizontal, imageMode: false, centered: self.centered) + let (image, imageContentWidth) = tabBarItemImage(item.item.selectedImage, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarSelectedIconColor, horizontal: self.horizontal, imageMode: true, centered: self.centered) + let (contextTextImage, _) = tabBarItemImage(item.item.image, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarExtractedTextColor, horizontal: self.horizontal, imageMode: false, centered: self.centered) + let (contextImage, _) = tabBarItemImage(item.item.image, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarExtractedIconColor, horizontal: self.horizontal, imageMode: true, centered: self.centered) node.textImageNode.image = textImage - node.accessibilityLabel = item.title + node.accessibilityLabel = item.item.title node.imageNode.image = image + node.contextTextImageNode.image = contextTextImage + node.contextImageNode.image = contextImage node.contentWidth = max(contentWidth, imageContentWidth) } else { - let (textImage, contentWidth) = tabBarItemImage(item.image, title: item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarTextColor, horizontal: self.horizontal, imageMode: false, centered: self.centered) - let (image, imageContentWidth) = tabBarItemImage(item.image, title: item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarIconColor, horizontal: self.horizontal, imageMode: true, centered: self.centered) + let (textImage, contentWidth) = tabBarItemImage(item.item.image, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarTextColor, horizontal: self.horizontal, imageMode: false, centered: self.centered) + let (image, imageContentWidth) = tabBarItemImage(item.item.image, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarIconColor, horizontal: self.horizontal, imageMode: true, centered: self.centered) + let (contextTextImage, _) = tabBarItemImage(item.item.image, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarExtractedTextColor, horizontal: self.horizontal, imageMode: false, centered: self.centered) + let (contextImage, _) = tabBarItemImage(item.item.image, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarExtractedIconColor, horizontal: self.horizontal, imageMode: true, centered: self.centered) node.textImageNode.image = textImage - node.accessibilityLabel = item.title + node.accessibilityLabel = item.item.title node.imageNode.image = image + node.contextTextImageNode.image = contextTextImage + node.contextImageNode.image = contextImage node.contentWidth = max(contentWidth, imageContentWidth) } @@ -414,10 +496,19 @@ class TabBarNode: ASDisplayNode { let nodeSize = node.textImageNode.image?.size ?? CGSize() let originX = floor(leftNodeOriginX + CGFloat(i) * distanceBetweenNodes - nodeSize.width / 2.0) + let horizontalHitTestInset = distanceBetweenNodes / 2.0 - nodeSize.width / 2.0 let nodeFrame = CGRect(origin: CGPoint(x: originX, y: 3.0), size: nodeSize) transition.updateFrame(node: node, frame: nodeFrame) + node.extractedContainerNode.frame = CGRect(origin: CGPoint(), size: nodeFrame.size) + node.extractedContainerNode.contentNode.frame = node.extractedContainerNode.bounds + node.extractedContainerNode.contentRect = node.extractedContainerNode.bounds + node.containerNode.frame = CGRect(origin: CGPoint(), size: nodeFrame.size) + node.hitTestSlop = UIEdgeInsets(top: -3.0, left: -horizontalHitTestInset, bottom: -3.0, right: -horizontalHitTestInset) + node.containerNode.hitTestSlop = UIEdgeInsets(top: -3.0, left: -horizontalHitTestInset, bottom: -3.0, right: -horizontalHitTestInset) node.imageNode.frame = CGRect(origin: CGPoint(), size: nodeFrame.size) node.textImageNode.frame = CGRect(origin: CGPoint(), size: nodeFrame.size) + node.contextImageNode.frame = CGRect(origin: CGPoint(), size: nodeFrame.size) + node.contextTextImageNode.frame = CGRect(origin: CGPoint(), size: nodeFrame.size) if container.badgeValue != container.appliedBadgeValue { container.appliedBadgeValue = container.badgeValue @@ -438,10 +529,10 @@ class TabBarNode: ASDisplayNode { let backgroundSize = CGSize(width: hasSingleLetterValue ? 18.0 : max(18.0, badgeSize.width + 10.0 + 1.0), height: 18.0) let backgroundFrame: CGRect if horizontal { - backgroundFrame = CGRect(origin: CGPoint(x: originX + 15.0, y: 3.0), size: backgroundSize) + backgroundFrame = CGRect(origin: CGPoint(x: 15.0, y: 0.0), size: backgroundSize) } else { - let contentWidth: CGFloat = 25.0 //node.contentWidth ?? node.frame.width - backgroundFrame = CGRect(origin: CGPoint(x: floor(originX + node.frame.width / 2.0) + contentWidth - backgroundSize.width - 5.0, y: self.centered ? 9.0 : 2.0), size: backgroundSize) + let contentWidth: CGFloat = 25.0 + backgroundFrame = CGRect(origin: CGPoint(x: floor(node.frame.width / 2.0) + contentWidth - backgroundSize.width - 5.0, y: self.centered ? 6.0 : -1.0), size: backgroundSize) } transition.updateFrame(node: container.badgeContainerNode, frame: backgroundFrame) container.badgeBackgroundNode.frame = CGRect(origin: CGPoint(), size: backgroundFrame.size) diff --git a/submodules/Display/Source/ViewController.swift b/submodules/Display/Source/ViewController.swift index 68a842d1b1..180bbf22af 100644 --- a/submodules/Display/Source/ViewController.swift +++ b/submodules/Display/Source/ViewController.swift @@ -219,9 +219,6 @@ public enum ViewControllerNavigationPresentation { if let contentNode = navigationBar.contentNode, case .expansion = contentNode.mode { height += contentNode.nominalHeight - contentNode.height } - if navigationBar.secondaryContentNode != nil { - //height -= 46.0 - } return height } else { return 0.0 @@ -627,6 +624,11 @@ public enum ViewControllerNavigationPresentation { open func toolbarActionSelected(action: ToolbarActionOption) { } + + open var hasTabBarItemContextAction: Bool = false + + open func tabBarItemContextAction(sourceNode: ContextExtractedContentContainingNode, gesture: ContextGesture) { + } } func traceIsOpaque(layer: CALayer, rect: CGRect) -> Bool { diff --git a/submodules/FFMpegBinding/Sources/FFMpegRemuxer.m b/submodules/FFMpegBinding/Sources/FFMpegRemuxer.m index c137b4828b..b25f557222 100644 --- a/submodules/FFMpegBinding/Sources/FFMpegRemuxer.m +++ b/submodules/FFMpegBinding/Sources/FFMpegRemuxer.m @@ -214,6 +214,9 @@ end: fprintf(stderr, "Error occurred: %s\n", av_err2str(ret)); return false; } + + printf("Remuxed video into %s\n", outPath.UTF8String); + return true; } diff --git a/submodules/GalleryUI/Sources/GalleryPagerNode.swift b/submodules/GalleryUI/Sources/GalleryPagerNode.swift index ce16fd64f4..edae0ad81f 100644 --- a/submodules/GalleryUI/Sources/GalleryPagerNode.swift +++ b/submodules/GalleryUI/Sources/GalleryPagerNode.swift @@ -5,6 +5,8 @@ import Display import SwiftSignalKit import Postbox +private let edgeWidth: CGFloat = 44.0 + private let leftFadeImage = generateImage(CGSize(width: 64.0, height: 1.0), opaque: false, rotatedContext: { size, context in let bounds = CGRect(origin: CGPoint(), size: size) context.clear(bounds) @@ -76,8 +78,9 @@ public final class GalleryPagerNode: ASDisplayNode, UIScrollViewDelegate, UIGest private let leftFadeNode: ASImageNode private let rightFadeNode: ASImageNode + private var highlightedSide: Bool? - private var pressGestureRecognizer: UILongPressGestureRecognizer? + private var tapRecognizer: TapLongTapOrDoubleTapGestureRecognizer? public private(set) var items: [GalleryItem] = [] private var itemNodes: [GalleryItemNode] = [] @@ -139,46 +142,106 @@ public final class GalleryPagerNode: ASDisplayNode, UIScrollViewDelegate, UIGest public override func didLoad() { super.didLoad() - let gestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(self.pressGesture(_:))) - gestureRecognizer.delegate = self - gestureRecognizer.minimumPressDuration = 0.01 - self.view.addGestureRecognizer(gestureRecognizer) - self.pressGestureRecognizer = gestureRecognizer + let recognizer = TapLongTapOrDoubleTapGestureRecognizer(target: self, action: #selector(self.tapLongTapOrDoubleTapGesture(_:))) + recognizer.delegate = self + self.tapRecognizer = recognizer + recognizer.tapActionAtPoint = { [weak self] point in + guard let strongSelf = self else { + return .fail + } + + let size = strongSelf.bounds + + var highlightedSide: Bool? + if point.x < edgeWidth && strongSelf.canGoToPreviousItem() { + if strongSelf.items.count > 1 { + highlightedSide = false + } + } else if point.x > size.width - edgeWidth && strongSelf.canGoToNextItem() { + if strongSelf.items.count > 1 { + highlightedSide = true + } + } + + if highlightedSide == nil { + return .fail + } + + if let result = strongSelf.hitTest(point, with: nil), let node = result.asyncdisplaykit_node as? ASButtonNode { + return .fail + } + return .keepWithSingleTap + } + recognizer.highlight = { [weak self] point in + guard let strongSelf = self else { + return + } + let size = strongSelf.bounds + + var highlightedSide: Bool? + if let point = point { + if point.x < edgeWidth && strongSelf.canGoToPreviousItem() { + if strongSelf.items.count > 1 { + highlightedSide = false + } + } else if point.x > size.width - edgeWidth && strongSelf.canGoToNextItem() { + if strongSelf.items.count > 1 { + highlightedSide = true + } + } + } + if strongSelf.highlightedSide != highlightedSide { + strongSelf.highlightedSide = highlightedSide + + let leftAlpha: CGFloat + let rightAlpha: CGFloat + if let highlightedSide = highlightedSide { + leftAlpha = highlightedSide ? 0.0 : 1.0 + rightAlpha = highlightedSide ? 1.0 : 0.0 + } else { + leftAlpha = 0.0 + rightAlpha = 0.0 + } + if strongSelf.leftFadeNode.alpha != leftAlpha { + strongSelf.leftFadeNode.alpha = leftAlpha + if leftAlpha.isZero { + strongSelf.leftFadeNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.16, timingFunction: kCAMediaTimingFunctionSpring) + } else { + strongSelf.leftFadeNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.08) + } + } + if strongSelf.rightFadeNode.alpha != rightAlpha { + strongSelf.rightFadeNode.alpha = rightAlpha + if rightAlpha.isZero { + strongSelf.rightFadeNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.16, timingFunction: kCAMediaTimingFunctionSpring) + } else { + strongSelf.rightFadeNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.08) + } + } + } + } + self.view.addGestureRecognizer(recognizer) } public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { return true } - @objc private func pressGesture(_ gestureRecognizer: UILongPressGestureRecognizer) { - let edgeWidth: CGFloat = 44.0 - let location = gestureRecognizer.location(in: gestureRecognizer.view) - switch gestureRecognizer.state { - case .began: - let transition: ContainedViewLayoutTransition = .animated(duration: 0.07, curve: .easeInOut) - if location.x < edgeWidth && self.canGoToPreviousItem() { - transition.updateAlpha(node: self.leftFadeNode, alpha: 1.0) - } else if location.x > self.frame.width - edgeWidth && self.canGoToNextItem() { - transition.updateAlpha(node: self.rightFadeNode, alpha: 1.0) + @objc private func tapLongTapOrDoubleTapGesture(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) { + switch recognizer.state { + case .ended: + if let (gesture, location) = recognizer.lastRecognizedGestureAndLocation { + if case .tap = gesture { + let size = self.bounds.size + if location.x < edgeWidth && self.canGoToPreviousItem() { + self.goToPreviousItem() + } else if location.x > size.width - edgeWidth && self.canGoToNextItem() { + self.goToNextItem() + } } - case .ended: - let transition: ContainedViewLayoutTransition = .animated(duration: 0.1, curve: .easeInOut) - if location.x < edgeWidth && self.canGoToPreviousItem() { - transition.updateAlpha(node: self.leftFadeNode, alpha: 0.0) - self.goToPreviousItem() - } else if location.x > self.frame.width - edgeWidth && self.canGoToNextItem() { - transition.updateAlpha(node: self.rightFadeNode, alpha: 0.0) - self.goToNextItem() - } - case .cancelled: - let transition: ContainedViewLayoutTransition = .animated(duration: 0.1, curve: .easeInOut) - if location.x < edgeWidth { - transition.updateAlpha(node: self.leftFadeNode, alpha: 0.0) - } else if location.x > self.frame.width - edgeWidth { - transition.updateAlpha(node: self.rightFadeNode, alpha: 0.0) - } - default: - break + } + default: + break } } diff --git a/submodules/GalleryUI/Sources/Items/ChatImageGalleryItem.swift b/submodules/GalleryUI/Sources/Items/ChatImageGalleryItem.swift index de6f4894e9..b1cd1df564 100644 --- a/submodules/GalleryUI/Sources/Items/ChatImageGalleryItem.swift +++ b/submodules/GalleryUI/Sources/Items/ChatImageGalleryItem.swift @@ -231,7 +231,22 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode { if let largestSize = largestRepresentationForPhoto(imageReference.media) { let displaySize = largestSize.dimensions.cgSize.fitted(CGSize(width: 1280.0, height: 1280.0)).dividedByScreenScale().integralFloor self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: displaySize, boundingSize: displaySize, intrinsicInsets: UIEdgeInsets()))() - self.imageNode.setSignal(chatMessagePhoto(postbox: context.account.postbox, photoReference: imageReference), dispatchOnDisplayLink: false) + let signal: Signal<(TransformImageArguments) -> DrawingContext?, NoError> = chatMessagePhotoInternal(photoData: chatMessagePhotoDatas(postbox: self.context.account.postbox, photoReference: imageReference, tryAdditionalRepresentations: true, synchronousLoad: false), synchronousLoad: false) + |> map { [weak self] _, quality, generate -> (TransformImageArguments) -> DrawingContext? in + Queue.mainQueue().async { + guard let strongSelf = self else { + return + } + switch quality { + case .medium, .full: + strongSelf.statusNodeContainer.isHidden = true + case .none, .blurred: + strongSelf.statusNodeContainer.isHidden = false + } + } + return generate + } + self.imageNode.setSignal(signal) self.zoomableContent = (largestSize.dimensions.cgSize, self.imageNode) self.fetchDisposable.set(fetchedMediaResource(mediaBox: self.context.account.postbox.mediaBox, reference: imageReference.resourceReference(largestSize.resource)).start()) @@ -401,7 +416,7 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode { copyView.layer.animate(from: NSValue(caTransform3D: CATransform3DIdentity), to: NSValue(caTransform3D: CATransform3DMakeScale(scale.width, scale.height, 1.0)), keyPath: "transform", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.25, removeOnCompletion: false) if let transformedSurfaceFrame = transformedSurfaceFrame, let transformedSurfaceFinalFrame = transformedSurfaceFinalFrame { - surfaceCopyView.layer.animatePosition(from: CGPoint(x: transformedSurfaceFrame.midX, y: transformedSurfaceFrame.midY), to: CGPoint(x: transformedCopyViewFinalFrame.midX, y: transformedCopyViewFinalFrame.midY), duration: positionDuration, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { [weak surfaceCopyView] _ in + surfaceCopyView.layer.animatePosition(from: CGPoint(x: transformedSurfaceFrame.midX, y: transformedSurfaceFrame.midY), to: CGPoint(x: transformedSurfaceFinalFrame.midX, y: transformedSurfaceFinalFrame.midY), duration: positionDuration, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { [weak surfaceCopyView] _ in surfaceCopyView?.removeFromSuperview() }) let scale = CGSize(width: transformedSurfaceFinalFrame.size.width / transformedSurfaceFrame.size.width, height: transformedSurfaceFinalFrame.size.height / transformedSurfaceFrame.size.height) diff --git a/submodules/GraphCore/Graph.h b/submodules/GraphCore/Graph.h new file mode 100644 index 0000000000..e4b99a3ca3 --- /dev/null +++ b/submodules/GraphCore/Graph.h @@ -0,0 +1,19 @@ +// +// Graph.h +// Graph +// +// Created by Mikhail Filimonov on 03.02.2020. +// Copyright © 2020 Telegram. All rights reserved. +// + +#import + +//! Project version number for Graph. +FOUNDATION_EXPORT double GraphVersionNumber; + +//! Project version string for Graph. +FOUNDATION_EXPORT const unsigned char GraphVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + + diff --git a/submodules/GraphCore/Info.plist b/submodules/GraphCore/Info.plist new file mode 100644 index 0000000000..861c829fcb --- /dev/null +++ b/submodules/GraphCore/Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + NSHumanReadableCopyright + Copyright © 2020 Telegram. All rights reserved. + + diff --git a/submodules/GraphCore/src/Charts Reader/ChartVisibilityItem.swift b/submodules/GraphCore/src/Charts Reader/ChartVisibilityItem.swift new file mode 100644 index 0000000000..fc42122024 --- /dev/null +++ b/submodules/GraphCore/src/Charts Reader/ChartVisibilityItem.swift @@ -0,0 +1,94 @@ +// +// ChartVisibilityItem.swift +// GraphCore +// +// Created by Mikhail Filimonov on 26.02.2020. +// Copyright © 2020 Telegram. All rights reserved. +// + +import Foundation +#if os(macOS) +import Cocoa +#else +import UIKit +#endif + +struct ChartVisibilityItem { + var title: String + var color: GColor + + static func generateItemsFrames(for chartWidth: CGFloat, items: [ChartVisibilityItem]) -> [CGRect] { + var previousPoint = CGPoint(x: ChatVisibilityItemConstants.insets.left, y: ChatVisibilityItemConstants.insets.top) + var frames: [CGRect] = [] + + for item in items { + let labelSize = textSize(with: item.title, font: ChatVisibilityItemConstants.textFont) + let width = (labelSize.width + ChatVisibilityItemConstants.labelTextApproxInsets).rounded(.up) + if previousPoint.x + width < (chartWidth - ChatVisibilityItemConstants.insets.left - ChatVisibilityItemConstants.insets.right) { + frames.append(CGRect(origin: previousPoint, size: CGSize(width: width, height: ChatVisibilityItemConstants.itemHeight))) + } else if previousPoint.x <= ChatVisibilityItemConstants.insets.left { + frames.append(CGRect(origin: previousPoint, size: CGSize(width: width, height: ChatVisibilityItemConstants.itemHeight))) + } else { + previousPoint.y += ChatVisibilityItemConstants.itemHeight + ChatVisibilityItemConstants.itemSpacing + previousPoint.x = ChatVisibilityItemConstants.insets.left + frames.append(CGRect(origin: previousPoint, size: CGSize(width: width, height: ChatVisibilityItemConstants.itemHeight))) + } + previousPoint.x += width + ChatVisibilityItemConstants.itemSpacing + } + + return frames + } + +} +enum ChatVisibilityItemConstants { + static let itemHeight: CGFloat = 30 + static let itemSpacing: CGFloat = 8 + static let labelTextApproxInsets: CGFloat = 40 + static let insets = NSEdgeInsets(top: 0, left: 16, bottom: 16, right: 16) + static let textFont = NSFont.systemFont(ofSize: 14, weight: .medium) +} + +public struct ChartDetailsViewModel { + public struct Value { + public let prefix: String? + public let title: String + public let value: String + public let color: GColor + public let visible: Bool + public init(prefix: String?, + title: String, + value: String, + color: GColor, + visible: Bool) { + self.prefix = prefix + self.title = title + self.value = value + self.color = color + self.visible = visible + } + } + + public internal(set) var title: String + public internal(set) var showArrow: Bool + public internal(set) var showPrefixes: Bool + public internal(set) var values: [Value] + public internal(set) var totalValue: Value? + public internal(set) var tapAction: (() -> Void)? + + static let blank = ChartDetailsViewModel(title: "", showArrow: false, showPrefixes: false, values: [], totalValue: nil, tapAction: nil) + public init(title: String, + showArrow: Bool, + showPrefixes: Bool, + values: [Value], + totalValue: Value?, + tapAction: (() -> Void)?) { + self.title = title + self.showArrow = showArrow + self.showPrefixes = showPrefixes + self.values = values + self.totalValue = totalValue + self.tapAction = tapAction + } + +} + diff --git a/submodules/GraphCore/src/Charts Reader/ChartsCollection.swift b/submodules/GraphCore/src/Charts Reader/ChartsCollection.swift new file mode 100644 index 0000000000..8b3d9f8a20 --- /dev/null +++ b/submodules/GraphCore/src/Charts Reader/ChartsCollection.swift @@ -0,0 +1,102 @@ +// +// ChardData.swift +// GraphTest +// +// Created by Andrei Salavei on 3/11/19. +// Copyright © 2019 Andrei Salavei. All rights reserved. +// + +import Foundation +#if os(macOS) +import Cocoa +#else +import UIKit +#endif +import Foundation +#if os(macOS) +import Cocoa +#else +import UIKit +#endif + +public struct ChartsCollection { + public struct Chart { + public internal(set) var color: GColor + public internal(set) var name: String + public internal(set) var values: [Double] + } + + public internal(set) var axisValues: [Date] + public internal(set) var chartValues: [Chart] + + static let blank = ChartsCollection(axisValues: [], chartValues: []) + var isBlank: Bool { + return axisValues.isEmpty || chartValues.isEmpty + } +} + +public extension ChartsCollection { + public init(from decodedData: [String: Any]) throws { + guard let columns = decodedData["columns"] as? [[Any]] else { + throw ChartsError.generalConversion("Unable to get columns from: \(decodedData)") + } + guard let types = decodedData["types"] as? [String: String] else { + throw ChartsError.generalConversion("Unable to get types from: \(decodedData)") + } + guard let names = decodedData["names"] as? [String: String] else { + throw ChartsError.generalConversion("Unable to get names from: \(decodedData)") + } + guard let colors = decodedData["colors"] as? [String: String] else { + throw ChartsError.generalConversion("Unable to get colors from: \(decodedData)") + } + +// chart.colors – Color for each variable in 6-hex-digit format (e.g. "#AAAAAA"). +// chart.names – Name for each variable. +// chart.percentage – true for percentage based values. +// chart.stacked – true for values stacking on top of each other. +// chart.y_scaled – true for charts with 2 Y axes. + + var axixValuesToSetup: [Date] = [] + var chartToSetup: [Chart] = [] + for column in columns { + guard let columnId = column.first as? String else { + throw ChartsError.generalConversion("Unable to get column name from: \(column)") + } + guard let typeString = types[columnId], let type = ColumnType(rawValue: typeString) else { + throw ChartsError.generalConversion("Unable to get column type from: \(types) - \(columnId)") + } + switch type { + case .axix: + axixValuesToSetup = try column.dropFirst().map { Date(timeIntervalSince1970: try Convert.doubleFrom($0) / 1000) } + case .chart, .bar, .area: + guard let colorString = colors[columnId], + let color = GColor(hexString: colorString) else { + throw ChartsError.generalConversion("Unable to get color name from: \(colors) - \(columnId)") + } + guard let name = names[columnId] else { + throw ChartsError.generalConversion("Unable to get column name from: \(names) - \(columnId)") + } + let values = try column.dropFirst().map { try Convert.doubleFrom($0) } + chartToSetup.append(Chart(color: color, + name: name, + values: values)) + } + } + + guard axixValuesToSetup.isEmpty == false, + chartToSetup.isEmpty == false, + chartToSetup.firstIndex(where: { $0.values.count != axixValuesToSetup.count }) == nil else { + throw ChartsError.generalConversion("Saniazing: Invalid number of items: \(axixValuesToSetup), \(chartToSetup)") + } + self.axisValues = axixValuesToSetup + self.chartValues = chartToSetup + + } +} + +private enum ColumnType: String { + case axix = "x" + case chart = "line" + case area = "area" + case bar = "bar" +} diff --git a/submodules/GraphCore/src/Charts Reader/ChartsDataManager.swift b/submodules/GraphCore/src/Charts Reader/ChartsDataManager.swift new file mode 100644 index 0000000000..cfcb98ee78 --- /dev/null +++ b/submodules/GraphCore/src/Charts Reader/ChartsDataManager.swift @@ -0,0 +1,196 @@ +// +// ChartsDataManager.swift +// GraphTest +// +// Created by Andrei Salavei on 3/11/19. +// Copyright © 2019 Andrei Salavei. All rights reserved. +// + +import Foundation +#if os(macOS) +import Cocoa +#else +import UIKit +#endif + +public class ChartsDataManager { + public static func readChart(item: [String: Any], extraCopiesCount: Int = 0, sync: Bool, success: @escaping (ChartsCollection) -> Void, failure: @escaping (Error) -> Void) { + let workItem: (() -> Void) = { + do { + var collection = try ChartsCollection(from: item) + for _ in 0.. Void, failure: @escaping (Error) -> Void) { + let workItem: (() -> Void) = { + do { + let decoded = try JSONSerialization.jsonObject(with: data, options: []) + guard let item = decoded as? [String: Any] else { + throw ChartsError.invalidJson + } + var collection = try ChartsCollection(from: item) + for _ in 0.. Void, failure: @escaping (Error) -> Void) { + let workItem: (() -> Void) = { + do { + let data = try Data(contentsOf: file) + let decoded = try JSONSerialization.jsonObject(with: data, options: []) + guard let item = decoded as? [String: Any] else { + throw ChartsError.invalidJson + } + var collection = try ChartsCollection(from: item) + for _ in 0.. Void, failure: @escaping (Error) -> Void) { + let workItem: (() -> Void) = { + do { + let data = try Data(contentsOf: file) + let decoded = try JSONSerialization.jsonObject(with: data, options: []) + guard let items = decoded as? [[String: Any]] else { + throw ChartsError.invalidJson + } + var collections = try items.map { try ChartsCollection(from: $0) } + for _ in 0.. Double { + guard let double = try doubleFrom(value, lenientCast: false) else { + throw ChartsError.generalConversion("Unable to cast \(String(describing: value)) to \(Double.self)") + } + return double + } + + public static func doubleFrom(_ value: Any?, lenientCast: Bool = false) throws -> Double? { + guard let value = value else { + return nil + } + if let intValue = value as? Int { + return Double(intValue) + } else if let floatValue = value as? Float { + return Double(floatValue) + } else if let int64Value = value as? Int64 { + return Double(int64Value) + } else if let intValue = value as? Int { + return Double(intValue) + } else if let stringValue = value as? String { + if let doubleValue = Double(stringValue) { + return doubleValue + } + } + if lenientCast { + return nil + } else { + throw ChartsError.generalConversion("Unable to cast \(String(describing: value)) to \(Double.self)") + } + } +} diff --git a/submodules/GraphCore/src/Charts/Controllers/BaseChartController.swift b/submodules/GraphCore/src/Charts/Controllers/BaseChartController.swift new file mode 100644 index 0000000000..b49f69d6fa --- /dev/null +++ b/submodules/GraphCore/src/Charts/Controllers/BaseChartController.swift @@ -0,0 +1,186 @@ +// +// BaseChartController.swift +// GraphTest +// +// Created by Andrei Salavei on 4/7/19. +// Copyright © 2019 Andrei Salavei. All rights reserved. +// + +import Foundation +#if os(macOS) +import Cocoa +#else +import UIKit +#endif + + +enum BaseConstants { + static let defaultRange: ClosedRange = 0...1 + static let minimumAxisYLabelsDistance: CGFloat = 90 + static let monthDayDateFormatter = DateFormatter.utc(format: "MMM d") + static let timeDateFormatter = DateFormatter.utc(format: "HH:mm") + static let headerFullRangeFormatter: DateFormatter = { + let formatter = DateFormatter.utc() + formatter.calendar = Calendar.utc + formatter.dateStyle = .long + return formatter + }() + static let headerMediumRangeFormatter: DateFormatter = { + let formatter = DateFormatter.utc() + formatter.dateStyle = .medium + return formatter + }() + static let headerFullZoomedFormatter: DateFormatter = { + let formatter = DateFormatter.utc() + formatter.dateStyle = .full + return formatter + }() + + static let verticalBaseAnchors: [CGFloat] = [8, 5, 2.5, 2, 1] + static let defaultVerticalBaseAnchor: CGFloat = 1 + + static let mainChartLineWidth: CGFloat = 2 + static let previewChartLineWidth: CGFloat = 1 + + static let previewLinesChartOptimizationLevel: CGFloat = 1.5 + static let linesChartOptimizationLevel: CGFloat = 1.0 + static let barsChartOptimizationLevel: CGFloat = 0.75 + + static let defaultRangePresetLength = TimeInterval.day * 60 + + static let chartNumberFormatter: ScalesNumberFormatter = { + let numberFormatter = ScalesNumberFormatter() + numberFormatter.allowsFloats = true + numberFormatter.numberStyle = .decimal + numberFormatter.usesGroupingSeparator = true + numberFormatter.groupingSeparator = " " + numberFormatter.minimumIntegerDigits = 1 + numberFormatter.minimumFractionDigits = 0 + numberFormatter.maximumFractionDigits = 2 + return numberFormatter + }() + + static let detailsNumberFormatter: NumberFormatter = { + let detailsNumberFormatter = NumberFormatter() + detailsNumberFormatter.allowsFloats = false + detailsNumberFormatter.numberStyle = .decimal + detailsNumberFormatter.usesGroupingSeparator = true + detailsNumberFormatter.groupingSeparator = " " + return detailsNumberFormatter + }() +} + +public class BaseChartController: GColorModeContainer { + //let performanceRenderer = PerformanceRenderer() + var initialChartsCollection: ChartsCollection + var isZoomed: Bool = false + + var chartTitle: String = "" + + public init(chartsCollection: ChartsCollection) { + self.initialChartsCollection = chartsCollection + } + + public var mainChartRenderers: [ChartViewRenderer] { + fatalError("Abstract") + } + + public var navigationRenderers: [ChartViewRenderer] { + fatalError("Abstract") + } + + public var cartViewBounds: (() -> CGRect) = { fatalError() } + public var chartFrame: (() -> CGRect) = { fatalError() } + + public func initializeChart() { + fatalError("Abstract") + } + + public func chartInteractionDidBegin(point: CGPoint) { + fatalError("Abstract") + } + + public func chartInteractionDidEnd() { + fatalError("Abstract") + } + + public func cancelChartInteraction() { + fatalError("Abstract") + } + + public func didTapZoomOut() { + fatalError("Abstract") + } + + public func updateChartsVisibility(visibility: [Bool], animated: Bool) { + fatalError("Abstract") + } + + public var currentHorizontalRange: ClosedRange { + fatalError("Abstract") + } + + public func height(for width: CGFloat) -> CGFloat { + var height: CGFloat = 308 + + let items = actualChartsCollection.chartValues.map { value in + return ChartVisibilityItem(title: value.name, color: value.color) + } + let frames = ChartVisibilityItem.generateItemsFrames(for: width, items: items) + guard let lastFrame = frames.last else { return height } + + height += lastFrame.maxY + + return height + } + + public var isChartRangePagingEnabled: Bool = false + public var minimumSelectedChartRange: CGFloat = 0.05 + public var chartRangePagingClosure: ((Bool, CGFloat) -> Void)? // isEnabled, PageSize + public func setChartRangePagingEnabled(isEnabled: Bool, minimumSelectionSize: CGFloat) { + isChartRangePagingEnabled = isEnabled + minimumSelectedChartRange = minimumSelectionSize + chartRangePagingClosure?(isChartRangePagingEnabled, minimumSelectedChartRange) + } + + public var chartRangeUpdatedClosure: ((ClosedRange, Bool) -> Void)? + public var currentChartHorizontalRangeFraction: ClosedRange { + fatalError("Abstract") + } + + public func updateChartRange(_ rangeFraction: ClosedRange) { + fatalError("Abstract") + } + + public var actualChartVisibility: [Bool] { + fatalError("Abstract") + } + + public var actualChartsCollection: ChartsCollection { + fatalError("Abstract") + } + + public var drawChartVisibity: Bool { + return true + } + + public var drawChartNavigation: Bool { + return true + } + + public var setDetailsViewPositionClosure: ((CGFloat) -> Void)? + public var setDetailsChartVisibleClosure: ((Bool, Bool) -> Void)? + public var setDetailsViewModel: ((ChartDetailsViewModel, Bool) -> Void)? + public var getDetailsData: ((Date, @escaping (ChartsCollection?) -> Void) -> Void)? + public var setChartTitleClosure: ((String, Bool) -> Void)? + public var setBackButtonVisibilityClosure: ((Bool, Bool) -> Void)? + public var refreshChartToolsClosure: ((Bool) -> Void)? + + public func didTapZoomIn(date: Date) { + fatalError("Abstract") + } + + public func apply(colorMode: GColorMode, animated: Bool) { + + } +} diff --git a/submodules/GraphCore/src/Charts/Controllers/GeneralChartComponentController.swift b/submodules/GraphCore/src/Charts/Controllers/GeneralChartComponentController.swift new file mode 100644 index 0000000000..201adb8f71 --- /dev/null +++ b/submodules/GraphCore/src/Charts/Controllers/GeneralChartComponentController.swift @@ -0,0 +1,333 @@ +// +// GeneralChartComponentController.swift +// GraphTest +// +// Created by Andrei Salavei on 4/11/19. +// Copyright © 2019 Andrei Salavei. All rights reserved. +// + +import Foundation +#if os(macOS) +import Cocoa +#else +import UIKit +#endif + +enum GeneralChartComponentConstants { + static let defaultInitialRangeLength = CGFloat(TimeInterval.day * 60) + static let defaultZoomedRangeLength = CGFloat(TimeInterval.day) +} + +class GeneralChartComponentController: GColorModeContainer { + var chartsCollection: ChartsCollection = ChartsCollection.blank + var chartVisibility: [Bool] = [] + var lastChartInteractionPoint: CGPoint = .zero + var isChartInteractionBegun: Bool = false + var isChartInteracting: Bool = false + let isZoomed: Bool + + var colorMode: GColorMode = .day + var totalHorizontalRange: ClosedRange = BaseConstants.defaultRange + var totalVerticalRange: ClosedRange = BaseConstants.defaultRange + var initialHorizontalRange: ClosedRange = BaseConstants.defaultRange + var initialVerticalRange: ClosedRange = BaseConstants.defaultRange + + public var cartViewBounds: (() -> CGRect) = { fatalError() } + public var chartFrame: (() -> CGRect) = { fatalError() } + + init(isZoomed: Bool) { + self.isZoomed = isZoomed + } + + func initialize(chartsCollection: ChartsCollection, + initialDate: Date, + totalHorizontalRange: ClosedRange, + totalVerticalRange: ClosedRange) { + self.chartsCollection = chartsCollection + self.chartVisibility = Array(repeating: true, count: chartsCollection.chartValues.count) + self.totalHorizontalRange = totalHorizontalRange + self.totalVerticalRange = totalVerticalRange + self.initialHorizontalRange = totalHorizontalRange + self.initialVerticalRange = totalVerticalRange + + didLoad() + setupInitialChartRange(initialDate: initialDate) + } + + func didLoad() { + hideDetailsView(animated: false) + } + func willAppear(animated: Bool) { + updateChartRangeTitle(animated: animated) + setupChartRangePaging() + } + func willDisappear(animated: Bool) { + } + + func setupInitialChartRange(initialDate: Date) { + guard let first = chartsCollection.axisValues.first?.timeIntervalSince1970, + let last = chartsCollection.axisValues.last?.timeIntervalSince1970 else { return } + + let rangeStart = CGFloat(first) + let rangeEnd = CGFloat(last) + + if isZoomed { + let initalDate = CGFloat(initialDate.timeIntervalSince1970) + + initialHorizontalRange = max(initalDate, rangeStart)...min(initalDate + GeneralChartComponentConstants.defaultZoomedRangeLength, rangeEnd) + initialVerticalRange = totalVerticalRange + } else { + initialHorizontalRange = max(rangeStart, rangeEnd - GeneralChartComponentConstants.defaultInitialRangeLength)...rangeEnd + initialVerticalRange = totalVerticalRange + } + } + func setupChartRangePaging() { + chartRangePagingClosure?(false, 0.05) + } + + var visibleHorizontalMainChartRange: ClosedRange { + return currentMainRangeRenderer.verticalRange.current + } + var visibleVerticalMainChartRange: ClosedRange { + return currentMainRangeRenderer.verticalRange.current + } + var currentHorizontalMainChartRange: ClosedRange { + return currentMainRangeRenderer.horizontalRange.end + } + var currentVerticalMainChartRange: ClosedRange { + return currentMainRangeRenderer.verticalRange.end + } + var currentMainRangeRenderer: BaseChartRenderer { + fatalError("Abstract") + } + + var visiblePreviewHorizontalRange: ClosedRange { + return currentPreviewRangeRenderer.verticalRange.current + } + var visiblePreviewVerticalRange: ClosedRange { + return currentPreviewRangeRenderer.verticalRange.current + } + var currentPreviewHorizontalRange: ClosedRange { + return currentPreviewRangeRenderer.horizontalRange.end + } + var currentPreviewVerticalRange: ClosedRange { + return currentPreviewRangeRenderer.verticalRange.end + } + var currentPreviewRangeRenderer: BaseChartRenderer { + fatalError("Abstract") + } + + var mainChartRenderers: [ChartViewRenderer] { + fatalError("Abstract") + } + var previewRenderers: [ChartViewRenderer] { + fatalError("Abstract") + } + + func updateChartsVisibility(visibility: [Bool], animated: Bool) { + self.chartVisibility = visibility + if isChartInteractionBegun { + chartInteractionDidBegin(point: lastChartInteractionPoint) + } + } + + var currentChartHorizontalRangeFraction: ClosedRange { + let lowerPercent = (currentHorizontalMainChartRange.lowerBound - totalHorizontalRange.lowerBound) / totalHorizontalRange.distance + let upperPercent = (currentHorizontalMainChartRange.upperBound - totalHorizontalRange.lowerBound) / totalHorizontalRange.distance + return lowerPercent...upperPercent + } + + func chartRangeFractionDidUpdated(_ rangeFraction: ClosedRange) { + let horizontalRange = ClosedRange(uncheckedBounds: + (lower: totalHorizontalRange.lowerBound + rangeFraction.lowerBound * totalHorizontalRange.distance, + upper: totalHorizontalRange.lowerBound + rangeFraction.upperBound * totalHorizontalRange.distance)) + chartRangeDidUpdated(horizontalRange) + updateChartRangeTitle(animated: true) + } + + func chartRangeDidUpdated(_ updatedRange: ClosedRange) { + hideDetailsView(animated: true) + + if isChartInteractionBegun { + chartInteractionDidBegin(point: lastChartInteractionPoint) + } + } + + // MARK: - Details & Interaction + func findClosestDateTo(dateToFind: Date) -> (Date, Int)? { + guard chartsCollection.axisValues.count > 0 else { return nil } + var closestDate = chartsCollection.axisValues[0] + var minIndex = 0 + for (index, date) in chartsCollection.axisValues.enumerated() { + if abs(dateToFind.timeIntervalSince(date)) < abs(dateToFind.timeIntervalSince(closestDate)) { + closestDate = date + minIndex = index + } + } + return (closestDate, minIndex) + } + + func chartInteractionDidBegin(point: CGPoint) { + let chartFrame = self.chartFrame() + guard chartFrame.width > 0 else { return } + let horizontalRange = currentHorizontalMainChartRange + let dateToFind = Date(timeIntervalSince1970: TimeInterval(horizontalRange.distance * point.x + horizontalRange.lowerBound)) + guard let (closestDate, minIndex) = findClosestDateTo(dateToFind: dateToFind) else { return } + + let chartWasInteracting = isChartInteractionBegun + lastChartInteractionPoint = point + isChartInteractionBegun = true + isChartInteracting = true + + let chartValue: CGFloat = CGFloat(closestDate.timeIntervalSince1970) + let detailsViewPosition = (chartValue - horizontalRange.lowerBound) / horizontalRange.distance * chartFrame.width + chartFrame.minX + showDetailsView(at: chartValue, detailsViewPosition: detailsViewPosition, dataIndex: minIndex, date: closestDate, animted: chartWasInteracting) + } + + func showDetailsView(at chartPosition: CGFloat, detailsViewPosition: CGFloat, dataIndex: Int, date: Date, animted: Bool) { + setDetailsViewModel?(chartDetailsViewModel(closestDate: date, pointIndex: dataIndex), animted) + setDetailsChartVisibleClosure?(true, true) + setDetailsViewPositionClosure?(detailsViewPosition) + } + + func chartInteractionDidEnd() { + isChartInteracting = false + } + + func hideDetailsView(animated: Bool) { + isChartInteractionBegun = false + setDetailsChartVisibleClosure?(false, animated) + } + + var visibleDetailsChartValues: [ChartsCollection.Chart] { + let visibleCharts: [ChartsCollection.Chart] = chartVisibility.enumerated().compactMap { args in + args.element ? chartsCollection.chartValues[args.offset] : nil + } + return visibleCharts + } + + var updatePreviewRangeClosure: ((ClosedRange, Bool) -> Void)? + var zoomInOnDateClosure: ((Date) -> Void)? + var setChartTitleClosure: ((String, Bool) -> Void)? + var setDetailsViewPositionClosure: ((CGFloat) -> Void)? + var setDetailsChartVisibleClosure: ((Bool, Bool) -> Void)? + var setDetailsViewModel: ((ChartDetailsViewModel, Bool) -> Void)? + var chartRangePagingClosure: ((Bool, CGFloat) -> Void)? // isEnabled, PageSize + + func apply(colorMode: GColorMode, animated: Bool) { + self.colorMode = colorMode + } + +// MARK: - Helpers + var prevoiusHorizontalStrideInterval: Int = -1 + func updateHorizontalLimitLabels(horizontalScalesRenderer: HorizontalScalesRenderer, + horizontalRange: ClosedRange, + scaleType: ChartScaleType, + forceUpdate: Bool, + animated: Bool) { + let scaleTimeInterval: TimeInterval + if chartsCollection.axisValues.count >= 1 { + scaleTimeInterval = chartsCollection.axisValues[1].timeIntervalSince1970 - chartsCollection.axisValues[0].timeIntervalSince1970 + } else { + scaleTimeInterval = scaleType.timeInterval + } + + let numberOfItems = horizontalRange.distance / CGFloat(scaleTimeInterval) + let maximumNumberOfItems = chartFrame().width / scaleType.minimumAxisXDistance + let tempStride = max(1, Int((numberOfItems / maximumNumberOfItems).rounded(.up))) + var strideInterval = 1 + while strideInterval < tempStride { + strideInterval *= 2 + } + + if forceUpdate || (strideInterval != prevoiusHorizontalStrideInterval && strideInterval > 0) { + var labels: [LinesChartLabel] = [] + for index in stride(from: chartsCollection.axisValues.count - 1, to: -1, by: -strideInterval).reversed() { + let date = chartsCollection.axisValues[index] + labels.append(LinesChartLabel(value: CGFloat(date.timeIntervalSince1970), + text: scaleType.dateFormatter.string(from: date))) + } + prevoiusHorizontalStrideInterval = strideInterval + horizontalScalesRenderer.setup(labels: labels, animated: animated) + } + } + + func verticalLimitsLabels(verticalRange: ClosedRange) -> (ClosedRange, [LinesChartLabel]) { + let ditance = verticalRange.distance + let chartHeight = chartFrame().height + + guard ditance > 0, chartHeight > 0 else { return (BaseConstants.defaultRange, []) } + + let approximateNumberOfChartValues = (chartHeight / BaseConstants.minimumAxisYLabelsDistance) + + var numberOfOffsetsPerItem = ditance / approximateNumberOfChartValues + var multiplier: CGFloat = 1.0 + while numberOfOffsetsPerItem > 10 { + numberOfOffsetsPerItem /= 10 + multiplier *= 10 + } + var dividor: CGFloat = 1.0 + var maximumNumberOfDecimals = 2 + while numberOfOffsetsPerItem < 1 { + numberOfOffsetsPerItem *= 10 + dividor *= 10 + maximumNumberOfDecimals += 1 + } + + var base: CGFloat = BaseConstants.verticalBaseAnchors.first { numberOfOffsetsPerItem > $0 } ?? BaseConstants.defaultVerticalBaseAnchor + base = base * multiplier / dividor + + var verticalLabels: [LinesChartLabel] = [] + var verticalValue = (verticalRange.lowerBound / base).rounded(.down) * base + let lowerBound = verticalValue + + let numberFormatter = BaseConstants.chartNumberFormatter + numberFormatter.maximumFractionDigits = maximumNumberOfDecimals + while verticalValue < verticalRange.upperBound { + let text: String = numberFormatter.string(from: NSNumber(value: Double(verticalValue))) ?? "" + + verticalLabels.append(LinesChartLabel(value: verticalValue, text: text)) + verticalValue += base + } + let updatedRange = lowerBound...verticalValue + + return (updatedRange, verticalLabels) + } + + func chartDetailsViewModel(closestDate: Date, pointIndex: Int) -> ChartDetailsViewModel { + let values: [ChartDetailsViewModel.Value] = chartsCollection.chartValues.enumerated().map { arg in + let (index, component) = arg + return ChartDetailsViewModel.Value(prefix: nil, + title: component.name, + value: BaseConstants.detailsNumberFormatter.string(from: NSNumber(value: component.values[pointIndex])) ?? "", + color: component.color, + visible: chartVisibility[index]) + } + let dateString: String + if isZoomed { + dateString = BaseConstants.timeDateFormatter.string(from: closestDate) + } else { + dateString = BaseConstants.headerMediumRangeFormatter.string(from: closestDate) + } + let viewModel = ChartDetailsViewModel(title: dateString, + showArrow: !self.isZoomed, + showPrefixes: false, + values: values, + totalValue: nil, + tapAction: { [weak self] in + self?.zoomInOnDateClosure?(closestDate) }) + return viewModel + } + + func updateChartRangeTitle(animated: Bool) { + let fromDate = Date(timeIntervalSince1970: TimeInterval(currentHorizontalMainChartRange.lowerBound) + 1) + let toDate = Date(timeIntervalSince1970: TimeInterval(currentHorizontalMainChartRange.upperBound)) + if Calendar.utc.startOfDay(for: fromDate) == Calendar.utc.startOfDay(for: toDate) { + let stirng = BaseConstants.headerFullZoomedFormatter.string(from: fromDate) + self.setChartTitleClosure?(stirng, animated) + } else { + let stirng = "\(BaseConstants.headerMediumRangeFormatter.string(from: fromDate)) - \(BaseConstants.headerMediumRangeFormatter.string(from: toDate))" + self.setChartTitleClosure?(stirng, animated) + } + } +} diff --git a/submodules/GraphCore/src/Charts/Controllers/Lines/BaseLinesChartController.swift b/submodules/GraphCore/src/Charts/Controllers/Lines/BaseLinesChartController.swift new file mode 100644 index 0000000000..ab43ff0b8f --- /dev/null +++ b/submodules/GraphCore/src/Charts/Controllers/Lines/BaseLinesChartController.swift @@ -0,0 +1,251 @@ +// +// BaseLinesChartController.swift +// GraphTest +// +// Created by Andrei Salavei on 4/14/19. +// Copyright © 2019 Andrei Salavei. All rights reserved. +// + +import Foundation +#if os(macOS) +import Cocoa +#else +import UIKit +#endif + +public class BaseLinesChartController: BaseChartController { + var chartVisibility: [Bool] + var zoomChartVisibility: [Bool] + var lastChartInteractionPoint: CGPoint = .zero + var isChartInteractionBegun: Bool = false + + var initialChartRange: ClosedRange = BaseConstants.defaultRange + var zoomedChartRange: ClosedRange = BaseConstants.defaultRange + + override public init(chartsCollection: ChartsCollection) { + self.chartVisibility = Array(repeating: true, count: chartsCollection.chartValues.count) + self.zoomChartVisibility = [] + super.init(chartsCollection: chartsCollection) + } + + func setupChartCollection(chartsCollection: ChartsCollection, animated: Bool, isZoomed: Bool) { + if animated { + TimeInterval.setDefaultSuration(.expandAnimationDuration) + DispatchQueue.main.asyncAfter(deadline: .now() + .expandAnimationDuration) { + TimeInterval.setDefaultSuration(.osXDuration) + } + } + + self.initialChartsCollection = chartsCollection + self.isZoomed = isZoomed + + self.setBackButtonVisibilityClosure?(isZoomed, animated) + + updateChartRangeTitle(animated: animated) + } + + func updateChartRangeTitle(animated: Bool) { + + let range: ClosedRange + if zoomedChartRange == BaseConstants.defaultRange { + range = initialChartRange + } else { + range = zoomedChartRange + } + let fromDate = Date(timeIntervalSince1970: TimeInterval(range.lowerBound) + .hour) + let toDate = Date(timeIntervalSince1970: TimeInterval(range.upperBound)) + + + + if Calendar.utc.startOfDay(for: fromDate) == Calendar.utc.startOfDay(for: toDate) { + let stirng = BaseConstants.headerFullZoomedFormatter.string(from: fromDate) + self.setChartTitleClosure?(stirng, animated) + } else { + let stirng = "\(BaseConstants.headerMediumRangeFormatter.string(from: fromDate)) - \(BaseConstants.headerMediumRangeFormatter.string(from: toDate))" + self.setChartTitleClosure?(stirng, animated) + } + } + + public override func chartInteractionDidBegin(point: CGPoint) { + lastChartInteractionPoint = point + isChartInteractionBegun = true + } + + public override func chartInteractionDidEnd() { + + } + + public override func cancelChartInteraction() { + isChartInteractionBegun = false + } + + public override func updateChartRange(_ rangeFraction: ClosedRange) { + + } + + public override var actualChartVisibility: [Bool] { + return isZoomed ? zoomChartVisibility : chartVisibility + } + + public override var actualChartsCollection: ChartsCollection { + return initialChartsCollection + } + + var visibleChartValues: [ChartsCollection.Chart] { + let visibleCharts: [ChartsCollection.Chart] = actualChartVisibility.enumerated().compactMap { args in + args.element ? initialChartsCollection.chartValues[args.offset] : nil + } + return visibleCharts + } + + + func chartDetailsViewModel(closestDate: Date, pointIndex: Int) -> ChartDetailsViewModel { + let values: [ChartDetailsViewModel.Value] = actualChartsCollection.chartValues.enumerated().map { arg in + let (index, component) = arg + return ChartDetailsViewModel.Value(prefix: nil, + title: component.name, + value: BaseConstants.detailsNumberFormatter.string(from: component.values[pointIndex]), + color: component.color, + visible: actualChartVisibility[index]) + } + let dateString: String + if isZoomed { + dateString = BaseConstants.timeDateFormatter.string(from: closestDate) + } else { + dateString = BaseConstants.headerMediumRangeFormatter.string(from: closestDate) + } + let viewModel = ChartDetailsViewModel(title: dateString, + showArrow: !self.isZoomed, + showPrefixes: false, + values: values, + totalValue: nil, + tapAction: { [weak self] in self?.didTapZoomIn(date: closestDate) }) + return viewModel + } + + public override func didTapZoomIn(date: Date) { + guard isZoomed == false else { return } + cancelChartInteraction() + self.getDetailsData?(date, { updatedCollection in + if let updatedCollection = updatedCollection { + self.initialChartRange = self.currentHorizontalRange + if let startDate = updatedCollection.axisValues.first, + let endDate = updatedCollection.axisValues.last { + self.zoomedChartRange = CGFloat(max(date.timeIntervalSince1970, startDate.timeIntervalSince1970))...CGFloat(min(date.timeIntervalSince1970 + .day - .hour, endDate.timeIntervalSince1970)) + } else { + self.zoomedChartRange = CGFloat(date.timeIntervalSince1970)...CGFloat(date.timeIntervalSince1970 + .day - 1) + } + self.setupChartCollection(chartsCollection: updatedCollection, animated: true, isZoomed: true) + } + }) + } + + func horizontalLimitsLabels(horizontalRange: ClosedRange, + scaleType: ChartScaleType, + prevoiusHorizontalStrideInterval: Int) -> (Int, [LinesChartLabel])? { + let numberOfItems = horizontalRange.distance / CGFloat(scaleType.timeInterval) + let maximumNumberOfItems = chartFrame().width / scaleType.minimumAxisXDistance + let tempStride = max(1, Int((numberOfItems / maximumNumberOfItems).rounded(.up))) + var strideInterval = 1 + while strideInterval < tempStride { + strideInterval *= 2 + } + + if strideInterval != prevoiusHorizontalStrideInterval && strideInterval > 0 { + var labels: [LinesChartLabel] = [] + for index in stride(from: initialChartsCollection.axisValues.count - 1, to: -1, by: -strideInterval).reversed() { + let date = initialChartsCollection.axisValues[index] + labels.append(LinesChartLabel(value: CGFloat(date.timeIntervalSince1970), + text: scaleType.dateFormatter.string(from: date))) + } + return (strideInterval, labels) + } + return nil + } + + func findClosestDateTo(dateToFind: Date) -> (Date, Int)? { + guard initialChartsCollection.axisValues.count > 0 else { return nil } + var closestDate = initialChartsCollection.axisValues[0] + var minIndex = 0 + for (index, date) in initialChartsCollection.axisValues.enumerated() { + if abs(dateToFind.timeIntervalSince(date)) < abs(dateToFind.timeIntervalSince(closestDate)) { + closestDate = date + minIndex = index + } + } + return (closestDate, minIndex) + } + + func verticalLimitsLabels(verticalRange: ClosedRange) -> (ClosedRange, [LinesChartLabel]) { + let ditance = verticalRange.distance + let chartHeight = chartFrame().height + + guard ditance > 0, chartHeight > 0 else { return (BaseConstants.defaultRange, []) } + + let approximateNumberOfChartValues = (chartHeight / BaseConstants.minimumAxisYLabelsDistance) + + var numberOfOffsetsPerItem = ditance / approximateNumberOfChartValues + var multiplier: CGFloat = 1.0 + while numberOfOffsetsPerItem > 10 { + numberOfOffsetsPerItem /= 10 + multiplier *= 10 + } + var dividor: CGFloat = 1.0 + var maximumNumberOfDecimals = 2 + while numberOfOffsetsPerItem < 1 { + numberOfOffsetsPerItem *= 10 + dividor *= 10 + maximumNumberOfDecimals += 1 + } + + var base: CGFloat = BaseConstants.verticalBaseAnchors.first { numberOfOffsetsPerItem > $0 } ?? BaseConstants.defaultVerticalBaseAnchor + base = base * multiplier / dividor + + var verticalLabels: [LinesChartLabel] = [] + var verticalValue = (verticalRange.lowerBound / base).rounded(.down) * base + let lowerBound = verticalValue + + let numberFormatter = BaseConstants.chartNumberFormatter + numberFormatter.maximumFractionDigits = maximumNumberOfDecimals + while verticalValue < verticalRange.upperBound { + let text: String = numberFormatter.string(from: NSNumber(value: Double(verticalValue))) ?? "" + + verticalLabels.append(LinesChartLabel(value: verticalValue, text: text)) + verticalValue += base + } + let updatedRange = lowerBound...verticalValue + + return (updatedRange, verticalLabels) + } +} + +enum ChartScaleType { + case day + case hour + case minutes5 +} + +extension ChartScaleType { + var timeInterval: TimeInterval { + switch self { + case .day: return .day + case .hour: return .hour + case .minutes5: return .minute * 5 + } + } + + var minimumAxisXDistance: CGFloat { + switch self { + case .day: return 50 + case .hour: return 40 + case .minutes5: return 40 + } + } + var dateFormatter: DateFormatter { + switch self { + case .day: return BaseConstants.monthDayDateFormatter + case .hour: return BaseConstants.timeDateFormatter + case .minutes5: return BaseConstants.timeDateFormatter + } + } +} diff --git a/submodules/GraphCore/src/Charts/Controllers/Lines/GeneralLinesChartController.swift b/submodules/GraphCore/src/Charts/Controllers/Lines/GeneralLinesChartController.swift new file mode 100644 index 0000000000..89e8ae6e57 --- /dev/null +++ b/submodules/GraphCore/src/Charts/Controllers/Lines/GeneralLinesChartController.swift @@ -0,0 +1,251 @@ +// +// LinesChartController.swift +// GraphTest +// +// Created by Andrei Salavei on 4/7/19. +// Copyright © 2019 Andrei Salavei. All rights reserved. +// + +import Foundation +#if os(macOS) +import Cocoa +#else +import UIKit +#endif + +private enum Constants { + static let defaultRange: ClosedRange = 0...1 +} + +public class GeneralLinesChartController: BaseLinesChartController { + private let initialChartCollection: ChartsCollection + + private let mainLinesRenderer = LinesChartRenderer() + private let horizontalScalesRenderer = HorizontalScalesRenderer() + private let verticalScalesRenderer = VerticalScalesRenderer() + private let verticalLineRenderer = VerticalLinesRenderer() + private let lineBulletsRenerer = LineBulletsRenerer() + + private let previewLinesRenderer = LinesChartRenderer() + + private var totalVerticalRange: ClosedRange = Constants.defaultRange + private var totalHorizontalRange: ClosedRange = Constants.defaultRange + + private var prevoiusHorizontalStrideInterval: Int = 1 + + private (set) var chartLines: [LinesChartRenderer.LineData] = [] + + override public init(chartsCollection: ChartsCollection) { + self.initialChartCollection = chartsCollection + self.mainLinesRenderer.lineWidth = 2 + self.mainLinesRenderer.optimizationLevel = BaseConstants.linesChartOptimizationLevel + self.previewLinesRenderer.optimizationLevel = BaseConstants.previewLinesChartOptimizationLevel + + self.lineBulletsRenerer.isEnabled = false + + super.init(chartsCollection: chartsCollection) + self.zoomChartVisibility = chartVisibility + } + + override func setupChartCollection(chartsCollection: ChartsCollection, animated: Bool, isZoomed: Bool) { + super.setupChartCollection(chartsCollection: chartsCollection, animated: animated, isZoomed: isZoomed) + + self.chartLines = chartsCollection.chartValues.map { chart in + let points = chart.values.enumerated().map({ (arg) -> CGPoint in + return CGPoint(x: chartsCollection.axisValues[arg.offset].timeIntervalSince1970, + y: arg.element) + }) + return LinesChartRenderer.LineData(color: chart.color, points: points) + } + + self.prevoiusHorizontalStrideInterval = -1 + self.totalVerticalRange = LinesChartRenderer.LineData.verticalRange(lines: chartLines) ?? Constants.defaultRange + self.totalHorizontalRange = LinesChartRenderer.LineData.horizontalRange(lines: chartLines) ?? Constants.defaultRange + self.lineBulletsRenerer.bullets = self.chartLines.map { LineBulletsRenerer.Bullet(coordinate: $0.points.first ?? .zero, + color: $0.color)} + + let chartRange: ClosedRange + if isZoomed { + chartRange = zoomedChartRange + } else { + chartRange = initialChartRange + } + + self.previewLinesRenderer.setup(horizontalRange: totalHorizontalRange, animated: animated) + self.previewLinesRenderer.setup(verticalRange: totalVerticalRange, animated: animated) + + self.mainLinesRenderer.setLines(lines: chartLines, animated: animated) + self.previewLinesRenderer.setLines(lines: chartLines, animated: animated) + + updateHorizontalLimists(horizontalRange: chartRange, animated: animated) + updateMainChartHorizontalRange(range: chartRange, animated: animated) + updateVerticalLimitsAndRange(horizontalRange: chartRange, animated: animated) + self.chartRangeUpdatedClosure?(currentChartHorizontalRangeFraction, animated) + } + + public override func initializeChart() { + if let first = initialChartCollection.axisValues.first?.timeIntervalSince1970, + let last = initialChartCollection.axisValues.last?.timeIntervalSince1970 { + initialChartRange = CGFloat(max(first, last - BaseConstants.defaultRangePresetLength))...CGFloat(last) + } + setupChartCollection(chartsCollection: initialChartCollection, animated: false, isZoomed: false) + } + + public override var mainChartRenderers: [ChartViewRenderer] { + return [//performanceRenderer, + mainLinesRenderer, + horizontalScalesRenderer, + verticalScalesRenderer, + verticalLineRenderer, + lineBulletsRenerer + ] + } + + public override var navigationRenderers: [ChartViewRenderer] { + return [previewLinesRenderer] + } + + public override func updateChartsVisibility(visibility: [Bool], animated: Bool) { + chartVisibility = visibility + zoomChartVisibility = visibility + for (index, isVisible) in visibility.enumerated() { + mainLinesRenderer.setLineVisible(isVisible, at: index, animated: animated) + previewLinesRenderer.setLineVisible(isVisible, at: index, animated: animated) + lineBulletsRenerer.setLineVisible(isVisible, at: index, animated: animated) + } + + updateVerticalLimitsAndRange(horizontalRange: currentHorizontalRange, animated: true) + + if isChartInteractionBegun { + chartInteractionDidBegin(point: lastChartInteractionPoint) + } + } + + public override func chartInteractionDidBegin(point: CGPoint) { + let horizontalRange = mainLinesRenderer.horizontalRange.current + let chartFrame = self.chartFrame() + guard chartFrame.width > 0 else { return } + let chartInteractionWasBegin = isChartInteractionBegun + + let dateToFind = Date(timeIntervalSince1970: TimeInterval(horizontalRange.distance * point.x + horizontalRange.lowerBound)) + guard let (closestDate, minIndex) = findClosestDateTo(dateToFind: dateToFind) else { return } + + super.chartInteractionDidBegin(point: point) + + self.lineBulletsRenerer.bullets = chartLines.compactMap { chart in + return LineBulletsRenerer.Bullet(coordinate: chart.points[minIndex], color: chart.color) + } + self.lineBulletsRenerer.isEnabled = true + + let chartValue: CGFloat = CGFloat(closestDate.timeIntervalSince1970) + let detailsViewPosition = (chartValue - horizontalRange.lowerBound) / horizontalRange.distance * chartFrame.width + chartFrame.minX + self.setDetailsViewModel?(chartDetailsViewModel(closestDate: closestDate, pointIndex: minIndex), chartInteractionWasBegin) + self.setDetailsChartVisibleClosure?(true, true) + self.setDetailsViewPositionClosure?(detailsViewPosition) + self.verticalLineRenderer.values = [chartValue] + } + + + public override var currentChartHorizontalRangeFraction: ClosedRange { + let lowerPercent = (currentHorizontalRange.lowerBound - totalHorizontalRange.lowerBound) / totalHorizontalRange.distance + let upperPercent = (currentHorizontalRange.upperBound - totalHorizontalRange.lowerBound) / totalHorizontalRange.distance + return lowerPercent...upperPercent + } + + public override var currentHorizontalRange: ClosedRange { + return mainLinesRenderer.horizontalRange.end + } + + public override func cancelChartInteraction() { + super.cancelChartInteraction() + self.lineBulletsRenerer.isEnabled = false + + self.setDetailsChartVisibleClosure?(false, true) + self.verticalLineRenderer.values = [] + } + + public override func didTapZoomOut() { + cancelChartInteraction() + self.setupChartCollection(chartsCollection: initialChartCollection, animated: true, isZoomed: false) + } + + var visibleCharts: [LinesChartRenderer.LineData] { + let visibleCharts: [LinesChartRenderer.LineData] = chartVisibility.enumerated().compactMap { args in + args.element ? chartLines[args.offset] : nil + } + return visibleCharts + } + + public override func updateChartRange(_ rangeFraction: ClosedRange) { + cancelChartInteraction() + + let horizontalRange = ClosedRange(uncheckedBounds: + (lower: totalHorizontalRange.lowerBound + rangeFraction.lowerBound * totalHorizontalRange.distance, + upper: totalHorizontalRange.lowerBound + rangeFraction.upperBound * totalHorizontalRange.distance)) + + zoomedChartRange = horizontalRange + updateChartRangeTitle(animated: true) + + updateMainChartHorizontalRange(range: horizontalRange, animated: false) + updateHorizontalLimists(horizontalRange: horizontalRange, animated: true) + updateVerticalLimitsAndRange(horizontalRange: horizontalRange, animated: true) + } + + func updateMainChartHorizontalRange(range: ClosedRange, animated: Bool) { + mainLinesRenderer.setup(horizontalRange: range, animated: animated) + horizontalScalesRenderer.setup(horizontalRange: range, animated: animated) + verticalScalesRenderer.setup(horizontalRange: range, animated: animated) + verticalLineRenderer.setup(horizontalRange: range, animated: animated) + lineBulletsRenerer.setup(horizontalRange: range, animated: animated) + } + + func updateMainChartVerticalRange(range: ClosedRange, animated: Bool) { + mainLinesRenderer.setup(verticalRange: range, animated: animated) + horizontalScalesRenderer.setup(verticalRange: range, animated: animated) + verticalScalesRenderer.setup(verticalRange: range, animated: animated) + verticalLineRenderer.setup(verticalRange: range, animated: animated) + lineBulletsRenerer.setup(verticalRange: range, animated: animated) + } + + func updateHorizontalLimists(horizontalRange: ClosedRange, animated: Bool) { + if let (stride, labels) = horizontalLimitsLabels(horizontalRange: horizontalRange, + scaleType: isZoomed ? .hour : .day, + prevoiusHorizontalStrideInterval: prevoiusHorizontalStrideInterval) { + self.horizontalScalesRenderer.setup(labels: labels, animated: animated) + self.prevoiusHorizontalStrideInterval = stride + } + } + + func updateVerticalLimitsAndRange(horizontalRange: ClosedRange, animated: Bool) { + if let verticalRange = LinesChartRenderer.LineData.verticalRange(lines: visibleCharts, + calculatingRange: horizontalRange, + addBounds: true) { + + + let (range, labels) = verticalLimitsLabels(verticalRange: verticalRange) + + if verticalScalesRenderer.verticalRange.end != range { + verticalScalesRenderer.setup(verticalLimitsLabels: labels, animated: animated) + updateMainChartVerticalRange(range: range, animated: animated) + } + verticalScalesRenderer.setVisible(true, animated: animated) + } else { + verticalScalesRenderer.setVisible(false, animated: animated) + } + + guard let previewVerticalRange = LinesChartRenderer.LineData.verticalRange(lines: visibleCharts) else { return } + + if previewLinesRenderer.verticalRange.end != previewVerticalRange { + previewLinesRenderer.setup(verticalRange: previewVerticalRange, animated: animated) + } + } + + override public func apply(colorMode: GColorMode, animated: Bool) { + horizontalScalesRenderer.labelsColor = colorMode.chartLabelsColor + verticalScalesRenderer.labelsColor = colorMode.chartLabelsColor + verticalScalesRenderer.axisXColor = colorMode.chartStrongLinesColor + verticalScalesRenderer.horizontalLinesColor = colorMode.chartHelperLinesColor + lineBulletsRenerer.setInnerColor(colorMode.chartBackgroundColor, animated: animated) + verticalLineRenderer.linesColor = colorMode.chartStrongLinesColor + } +} diff --git a/submodules/GraphCore/src/Charts/Controllers/Lines/TwoAxisLinesChartController.swift b/submodules/GraphCore/src/Charts/Controllers/Lines/TwoAxisLinesChartController.swift new file mode 100644 index 0000000000..f921c1f4f4 --- /dev/null +++ b/submodules/GraphCore/src/Charts/Controllers/Lines/TwoAxisLinesChartController.swift @@ -0,0 +1,311 @@ +// +// TwoAxisLinesChartController.swift +// GraphTest +// +// Created by Andrei Salavei on 4/7/19. +// Copyright © 2019 Andrei Salavei. All rights reserved. +// + +import Foundation +#if os(macOS) +import Cocoa +#else +import UIKit +#endif + +private enum Constants { + static let verticalBaseAnchors: [CGFloat] = [8, 5, 4, 2.5, 2, 1] + static let defaultRange: ClosedRange = 0...1 +} + +public class TwoAxisLinesChartController: BaseLinesChartController { + class GraphController { + let mainLinesRenderer = LinesChartRenderer() + let verticalScalesRenderer = VerticalScalesRenderer() + let lineBulletsRenerer = LineBulletsRenerer() + let previewLinesRenderer = LinesChartRenderer() + + var chartLines: [LinesChartRenderer.LineData] = [] + + var totalVerticalRange: ClosedRange = Constants.defaultRange + + init() { + self.mainLinesRenderer.lineWidth = 2 + self.previewLinesRenderer.lineWidth = 1 + self.lineBulletsRenerer.isEnabled = false + + self.mainLinesRenderer.optimizationLevel = BaseConstants.linesChartOptimizationLevel + self.previewLinesRenderer.optimizationLevel = BaseConstants.previewLinesChartOptimizationLevel + } + + func updateMainChartVerticalRange(range: ClosedRange, animated: Bool) { + mainLinesRenderer.setup(verticalRange: range, animated: animated) + verticalScalesRenderer.setup(verticalRange: range, animated: animated) + lineBulletsRenerer.setup(verticalRange: range, animated: animated) + } + } + + private var graphControllers: [GraphController] = [] + private let verticalLineRenderer = VerticalLinesRenderer() + private let horizontalScalesRenderer = HorizontalScalesRenderer() + + var totalHorizontalRange: ClosedRange = Constants.defaultRange + + private let initialChartCollection: ChartsCollection + + private var prevoiusHorizontalStrideInterval: Int = 1 + + override public init(chartsCollection: ChartsCollection) { + self.initialChartCollection = chartsCollection + graphControllers = chartsCollection.chartValues.map { _ in GraphController() } + + super.init(chartsCollection: chartsCollection) + self.zoomChartVisibility = chartVisibility + } + + override func setupChartCollection(chartsCollection: ChartsCollection, animated: Bool, isZoomed: Bool) { + super.setupChartCollection(chartsCollection: chartsCollection, animated: animated, isZoomed: isZoomed) + + for (index, controller) in self.graphControllers.enumerated() { + let chart = chartsCollection.chartValues[index] + let points = chart.values.enumerated().map({ (arg) -> CGPoint in + return CGPoint(x: chartsCollection.axisValues[arg.offset].timeIntervalSince1970, + y: arg.element) + }) + let chartLines = [LinesChartRenderer.LineData(color: chart.color, points: points)] + controller.chartLines = [LinesChartRenderer.LineData(color: chart.color, points: points)] + controller.verticalScalesRenderer.labelsColor = chart.color + controller.totalVerticalRange = LinesChartRenderer.LineData.verticalRange(lines: chartLines) ?? Constants.defaultRange + self.totalHorizontalRange = LinesChartRenderer.LineData.horizontalRange(lines: chartLines) ?? Constants.defaultRange + controller.lineBulletsRenerer.bullets = chartLines.map { LineBulletsRenerer.Bullet(coordinate: $0.points.first ?? .zero, + color: $0.color) } + controller.previewLinesRenderer.setup(horizontalRange: self.totalHorizontalRange, animated: animated) + controller.previewLinesRenderer.setup(verticalRange: controller.totalVerticalRange, animated: animated) + controller.mainLinesRenderer.setLines(lines: chartLines, animated: animated) + controller.previewLinesRenderer.setLines(lines: chartLines, animated: animated) + + controller.verticalScalesRenderer.setHorizontalLinesVisible((index == 0), animated: animated) + controller.verticalScalesRenderer.isRightAligned = (index != 0) + } + + self.prevoiusHorizontalStrideInterval = -1 + + let chartRange: ClosedRange + if isZoomed { + chartRange = zoomedChartRange + } else { + chartRange = initialChartRange + } + + updateHorizontalLimists(horizontalRange: chartRange, animated: animated) + updateMainChartHorizontalRange(range: chartRange, animated: animated) + updateVerticalLimitsAndRange(horizontalRange: chartRange, animated: animated) + + self.chartRangeUpdatedClosure?(currentChartHorizontalRangeFraction, animated) + } + + public override func initializeChart() { + if let first = initialChartCollection.axisValues.first?.timeIntervalSince1970, + let last = initialChartCollection.axisValues.last?.timeIntervalSince1970 { + initialChartRange = CGFloat(max(first, last - BaseConstants.defaultRangePresetLength))...CGFloat(last) + } + setupChartCollection(chartsCollection: initialChartCollection, animated: false, isZoomed: false) + } + + public override var mainChartRenderers: [ChartViewRenderer] { + return graphControllers.map { $0.mainLinesRenderer } + + graphControllers.flatMap { [$0.verticalScalesRenderer, $0.lineBulletsRenerer] } + + [horizontalScalesRenderer, verticalLineRenderer, +// performanceRenderer + ] + } + + public override var navigationRenderers: [ChartViewRenderer] { + return graphControllers.map { $0.previewLinesRenderer } + } + + public override func updateChartsVisibility(visibility: [Bool], animated: Bool) { + chartVisibility = visibility + zoomChartVisibility = visibility + let firstIndex = visibility.firstIndex(where: { $0 }) + for (index, isVisible) in visibility.enumerated() { + let graph = graphControllers[index] + for graphIndex in graph.chartLines.indices { + graph.mainLinesRenderer.setLineVisible(isVisible, at: graphIndex, animated: animated) + graph.previewLinesRenderer.setLineVisible(isVisible, at: graphIndex, animated: animated) + graph.lineBulletsRenerer.setLineVisible(isVisible, at: graphIndex, animated: animated) + } + graph.verticalScalesRenderer.setVisible(isVisible, animated: animated) + if let firstIndex = firstIndex { + graph.verticalScalesRenderer.setHorizontalLinesVisible(index == firstIndex, animated: animated) + } + } + + updateVerticalLimitsAndRange(horizontalRange: currentHorizontalRange, animated: true) + + if isChartInteractionBegun { + chartInteractionDidBegin(point: lastChartInteractionPoint) + } + } + + public override func chartInteractionDidBegin(point: CGPoint) { + let horizontalRange = currentHorizontalRange + let chartFrame = self.chartFrame() + guard chartFrame.width > 0 else { return } + + let dateToFind = Date(timeIntervalSince1970: TimeInterval(horizontalRange.distance * point.x + horizontalRange.lowerBound)) + guard let (closestDate, minIndex) = findClosestDateTo(dateToFind: dateToFind) else { return } + + let chartInteractionWasBegin = isChartInteractionBegun + super.chartInteractionDidBegin(point: point) + + for graphController in graphControllers { + graphController.lineBulletsRenerer.bullets = graphController.chartLines.map { chart in + LineBulletsRenerer.Bullet(coordinate: chart.points[minIndex], color: chart.color) + } + graphController.lineBulletsRenerer.isEnabled = true + } + + let chartValue: CGFloat = CGFloat(closestDate.timeIntervalSince1970) + let detailsViewPosition = (chartValue - horizontalRange.lowerBound) / horizontalRange.distance * chartFrame.width + chartFrame.minX + self.setDetailsViewModel?(chartDetailsViewModel(closestDate: closestDate, pointIndex: minIndex), chartInteractionWasBegin) + self.setDetailsChartVisibleClosure?(true, true) + self.setDetailsViewPositionClosure?(detailsViewPosition) + self.verticalLineRenderer.values = [chartValue] + } + + public override var currentChartHorizontalRangeFraction: ClosedRange { + let lowerPercent = (currentHorizontalRange.lowerBound - totalHorizontalRange.lowerBound) / totalHorizontalRange.distance + let upperPercent = (currentHorizontalRange.upperBound - totalHorizontalRange.lowerBound) / totalHorizontalRange.distance + return lowerPercent...upperPercent + } + + public override var currentHorizontalRange: ClosedRange { + return graphControllers.first?.mainLinesRenderer.horizontalRange.end ?? Constants.defaultRange + } + + public override func cancelChartInteraction() { + super.cancelChartInteraction() + for graphController in graphControllers { + graphController.lineBulletsRenerer.isEnabled = false + } + + self.setDetailsChartVisibleClosure?(false, true) + self.verticalLineRenderer.values = [] + } + + public override func didTapZoomOut() { + cancelChartInteraction() + self.setupChartCollection(chartsCollection: initialChartCollection, animated: true, isZoomed: false) + } + + public override func updateChartRange(_ rangeFraction: ClosedRange) { + cancelChartInteraction() + + let horizontalRange = ClosedRange(uncheckedBounds: + (lower: totalHorizontalRange.lowerBound + rangeFraction.lowerBound * totalHorizontalRange.distance, + upper: totalHorizontalRange.lowerBound + rangeFraction.upperBound * totalHorizontalRange.distance)) + + zoomedChartRange = horizontalRange + updateChartRangeTitle(animated: true) + + updateMainChartHorizontalRange(range: horizontalRange, animated: false) + updateHorizontalLimists(horizontalRange: horizontalRange, animated: true) + updateVerticalLimitsAndRange(horizontalRange: horizontalRange, animated: true) + } + + func updateMainChartHorizontalRange(range: ClosedRange, animated: Bool) { + for controller in graphControllers { + controller.mainLinesRenderer.setup(horizontalRange: range, animated: animated) + controller.verticalScalesRenderer.setup(horizontalRange: range, animated: animated) + controller.lineBulletsRenerer.setup(horizontalRange: range, animated: animated) + } + horizontalScalesRenderer.setup(horizontalRange: range, animated: animated) + verticalLineRenderer.setup(horizontalRange: range, animated: animated) + } + + func updateHorizontalLimists(horizontalRange: ClosedRange, animated: Bool) { + if let (stride, labels) = horizontalLimitsLabels(horizontalRange: horizontalRange, + scaleType: isZoomed ? .hour : .day, + prevoiusHorizontalStrideInterval: prevoiusHorizontalStrideInterval) { + self.horizontalScalesRenderer.setup(labels: labels, animated: animated) + self.prevoiusHorizontalStrideInterval = stride + } + } + + func updateVerticalLimitsAndRange(horizontalRange: ClosedRange, animated: Bool) { + let chartHeight = chartFrame().height + let approximateNumberOfChartValues = (chartHeight / BaseConstants.minimumAxisYLabelsDistance) + + var dividorsAndMultiplers: [(startValue: CGFloat, base: CGFloat, count: Int, maximumNumberOfDecimals: Int)] = graphControllers.enumerated().map { arg in + let (index, controller) = arg + let verticalRange = LinesChartRenderer.LineData.verticalRange(lines: controller.chartLines, + calculatingRange: horizontalRange, + addBounds: true) ?? controller.totalVerticalRange + + var numberOfOffsetsPerItem = verticalRange.distance / approximateNumberOfChartValues + + var multiplier: CGFloat = 1.0 + while numberOfOffsetsPerItem > 10 { + numberOfOffsetsPerItem /= 10 + multiplier *= 10 + } + var dividor: CGFloat = 1.0 + var maximumNumberOfDecimals = 2 + while numberOfOffsetsPerItem < 1 { + numberOfOffsetsPerItem *= 10 + dividor *= 10 + maximumNumberOfDecimals += 1 + } + + let generalBase = Constants.verticalBaseAnchors.first { numberOfOffsetsPerItem > $0 } ?? BaseConstants.defaultVerticalBaseAnchor + let base = generalBase * multiplier / dividor + + var verticalValue = (verticalRange.lowerBound / base).rounded(.down) * base + let startValue = verticalValue + var count = 0 + if chartVisibility[index] { + while verticalValue < verticalRange.upperBound { + count += 1 + verticalValue += base + } + } + return (startValue: startValue, base: base, count: count, maximumNumberOfDecimals: maximumNumberOfDecimals) + } + + let totalCount = dividorsAndMultiplers.map { $0.count }.max() ?? 0 + guard totalCount > 0 else { return } + + let numberFormatter = BaseConstants.chartNumberFormatter + for (index, controller) in graphControllers.enumerated() { + + let (startValue, base, _, maximumNumberOfDecimals) = dividorsAndMultiplers[index] + + let updatedRange = startValue...(startValue + base * CGFloat(totalCount)) + if controller.verticalScalesRenderer.verticalRange.end != updatedRange { + numberFormatter.maximumFractionDigits = maximumNumberOfDecimals + + var verticalLabels: [LinesChartLabel] = [] + for multipler in 0...(totalCount - 1) { + let verticalValue = startValue + base * CGFloat(multipler) + let text: String = numberFormatter.string(from: NSNumber(value: Double(verticalValue))) ?? "" + verticalLabels.append(LinesChartLabel(value: verticalValue, text: text)) + } + + controller.verticalScalesRenderer.setup(verticalLimitsLabels: verticalLabels, animated: animated) + controller.updateMainChartVerticalRange(range: updatedRange, animated: animated) + } + } + } + + public override func apply(colorMode: GColorMode, animated: Bool) { + horizontalScalesRenderer.labelsColor = colorMode.chartLabelsColor + verticalLineRenderer.linesColor = colorMode.chartStrongLinesColor + + for controller in graphControllers { + controller.verticalScalesRenderer.horizontalLinesColor = colorMode.chartHelperLinesColor + controller.lineBulletsRenerer.setInnerColor(colorMode.chartBackgroundColor, animated: animated) + controller.verticalScalesRenderer.axisXColor = colorMode.chartStrongLinesColor + } + } +} diff --git a/submodules/GraphCore/src/Charts/Controllers/Percent And Pie/PercentChartComponentController.swift b/submodules/GraphCore/src/Charts/Controllers/Percent And Pie/PercentChartComponentController.swift new file mode 100644 index 0000000000..c6937be6bf --- /dev/null +++ b/submodules/GraphCore/src/Charts/Controllers/Percent And Pie/PercentChartComponentController.swift @@ -0,0 +1,200 @@ +// +// PercentChartComponentController.swift +// GraphTest +// +// Created by Andrei Salavei on 4/14/19. +// Copyright © 2019 Andrei Salavei. All rights reserved. +// + +import Foundation +#if os(macOS) +import Cocoa +#else +import UIKit +#endif + +class PercentChartComponentController: GeneralChartComponentController { + let mainPecentChartRenderer: PecentChartRenderer + let horizontalScalesRenderer: HorizontalScalesRenderer + let verticalScalesRenderer: VerticalScalesRenderer + let verticalLineRenderer: VerticalLinesRenderer + let previewPercentChartRenderer: PecentChartRenderer + var percentageData: PecentChartRenderer.PercentageData = .blank + + init(isZoomed: Bool, + mainPecentChartRenderer: PecentChartRenderer, + horizontalScalesRenderer: HorizontalScalesRenderer, + verticalScalesRenderer: VerticalScalesRenderer, + verticalLineRenderer: VerticalLinesRenderer, + previewPercentChartRenderer: PecentChartRenderer) { + self.mainPecentChartRenderer = mainPecentChartRenderer + self.horizontalScalesRenderer = horizontalScalesRenderer + self.verticalScalesRenderer = verticalScalesRenderer + self.verticalLineRenderer = verticalLineRenderer + self.previewPercentChartRenderer = previewPercentChartRenderer + + super.init(isZoomed: isZoomed) + } + + override func initialize(chartsCollection: ChartsCollection, initialDate: Date, totalHorizontalRange _: ClosedRange, totalVerticalRange _: ClosedRange) { + let components = chartsCollection.chartValues.map { PecentChartRenderer.PercentageData.Component(color: $0.color, + values: $0.values.map { CGFloat($0) }) } + self.percentageData = PecentChartRenderer.PercentageData(locations: chartsCollection.axisValues.map { CGFloat($0.timeIntervalSince1970) }, + components: components) + let totalHorizontalRange = PecentChartRenderer.PercentageData.horizontalRange(data: self.percentageData) ?? BaseConstants.defaultRange + let totalVerticalRange = BaseConstants.defaultRange + + super.initialize(chartsCollection: chartsCollection, + initialDate: initialDate, + totalHorizontalRange: totalHorizontalRange, + totalVerticalRange: totalVerticalRange) + + mainPecentChartRenderer.percentageData = self.percentageData + previewPercentChartRenderer.percentageData = self.percentageData + + let axisValues: [CGFloat] = [0, 25, 50, 75, 100] + let labels: [LinesChartLabel] = axisValues.map { value in + return LinesChartLabel(value: value / 100, text: BaseConstants.detailsNumberFormatter.string(from: NSNumber(value: Double(value))) ?? "") + } + verticalScalesRenderer.setup(verticalLimitsLabels: labels, animated: false) + + setupMainChart(horizontalRange: initialHorizontalRange, animated: false) + setupMainChart(verticalRange: initialVerticalRange, animated: false) + previewPercentChartRenderer.setup(verticalRange: totalVerticalRange, animated: false) + previewPercentChartRenderer.setup(horizontalRange: totalHorizontalRange, animated: false) + updateHorizontalLimitLabels(animated: false) + } + + override func willAppear(animated: Bool) { + previewPercentChartRenderer.setup(verticalRange: totalVerticalRange, animated: animated) + previewPercentChartRenderer.setup(horizontalRange: totalHorizontalRange, animated: animated) + + setConponentsVisible(visible: true, animated: true) + + setupMainChart(verticalRange: initialVerticalRange, animated: animated) + setupMainChart(horizontalRange: initialHorizontalRange, animated: animated) + + updatePreviewRangeClosure?(currentChartHorizontalRangeFraction, animated) + + super.willAppear(animated: animated) + } + + override func chartRangeDidUpdated(_ updatedRange: ClosedRange) { + super.chartRangeDidUpdated(updatedRange) + + initialHorizontalRange = updatedRange + setupMainChart(horizontalRange: updatedRange, animated: false) + updateHorizontalLimitLabels(animated: true) + } + + func updateHorizontalLimitLabels(animated: Bool) { + updateHorizontalLimitLabels(horizontalScalesRenderer: horizontalScalesRenderer, + horizontalRange: initialHorizontalRange, + scaleType: isZoomed ? .hour : .day, + forceUpdate: false, + animated: animated) + } + + func prepareAppearanceAnimation(horizontalRnage: ClosedRange) { + setupMainChart(horizontalRange: horizontalRnage, animated: false) + setConponentsVisible(visible: false, animated: false) + } + + func setConponentsVisible(visible: Bool, animated: Bool) { + mainPecentChartRenderer.setVisible(visible, animated: animated) + horizontalScalesRenderer.setVisible(visible, animated: animated) + verticalScalesRenderer.setVisible(visible, animated: animated) + verticalLineRenderer.setVisible(visible, animated: animated) + previewPercentChartRenderer.setVisible(visible, animated: animated) + } + + func setupMainChart(horizontalRange: ClosedRange, animated: Bool) { + mainPecentChartRenderer.setup(horizontalRange: horizontalRange, animated: animated) + horizontalScalesRenderer.setup(horizontalRange: horizontalRange, animated: animated) + verticalScalesRenderer.setup(horizontalRange: horizontalRange, animated: animated) + verticalLineRenderer.setup(horizontalRange: horizontalRange, animated: animated) + } + + func setupMainChart(verticalRange: ClosedRange, animated: Bool) { + mainPecentChartRenderer.setup(verticalRange: verticalRange, animated: animated) + horizontalScalesRenderer.setup(verticalRange: verticalRange, animated: animated) + verticalScalesRenderer.setup(verticalRange: verticalRange, animated: animated) + verticalLineRenderer.setup(verticalRange: verticalRange, animated: animated) + } + + public override func updateChartsVisibility(visibility: [Bool], animated: Bool) { + super.updateChartsVisibility(visibility: visibility, animated: animated) + for (index, isVisible) in visibility.enumerated() { + mainPecentChartRenderer.setComponentVisible(isVisible, at: index, animated: animated) + previewPercentChartRenderer.setComponentVisible(isVisible, at: index, animated: animated) + } + verticalScalesRenderer.setVisible(visibility.contains(true), animated: animated) + } + + override func chartDetailsViewModel(closestDate: Date, pointIndex: Int) -> ChartDetailsViewModel { + let visibleValues = visibleDetailsChartValues + + let total = visibleValues.map { $0.values[pointIndex] }.reduce(0, +) + + let values: [ChartDetailsViewModel.Value] = chartsCollection.chartValues.enumerated().map { arg in + let (index, component) = arg + return ChartDetailsViewModel.Value(prefix: PercentConstants.percentValueFormatter.string(from: component.values[pointIndex] / total * 100), + title: component.name, + value: BaseConstants.detailsNumberFormatter.string(from: component.values[pointIndex]), + color: component.color, + visible: chartVisibility[index]) + } + let dateString: String + if isZoomed { + dateString = BaseConstants.timeDateFormatter.string(from: closestDate) + } else { + dateString = BaseConstants.headerMediumRangeFormatter.string(from: closestDate) + } + let viewModel = ChartDetailsViewModel(title: dateString, + showArrow: !self.isZoomed, + showPrefixes: true, + values: values, + totalValue: nil, + tapAction: { [weak self] in + self?.hideDetailsView(animated: true) + self?.zoomInOnDateClosure?(closestDate) }) + return viewModel + } + + var currentlyVisiblePercentageData: PecentChartRenderer.PercentageData { + var currentPercentageData = percentageData + currentPercentageData.components = chartVisibility.enumerated().compactMap { $0.element ? currentPercentageData.components[$0.offset] : nil } + return currentPercentageData + } + + override var currentMainRangeRenderer: BaseChartRenderer { + return mainPecentChartRenderer + } + + override var currentPreviewRangeRenderer: BaseChartRenderer { + return previewPercentChartRenderer + } + + override func showDetailsView(at chartPosition: CGFloat, detailsViewPosition: CGFloat, dataIndex: Int, date: Date, animted: Bool) { + super.showDetailsView(at: chartPosition, detailsViewPosition: detailsViewPosition, dataIndex: dataIndex, date: date, animted: animted) + verticalLineRenderer.values = [chartPosition] + verticalLineRenderer.isEnabled = true + } + + override func hideDetailsView(animated: Bool) { + super.hideDetailsView(animated: animated) + + verticalLineRenderer.values = [] + verticalLineRenderer.isEnabled = false + } + + override func apply(colorMode: GColorMode, animated: Bool) { + super.apply(colorMode: colorMode, animated: animated) + + horizontalScalesRenderer.labelsColor = colorMode.chartLabelsColor + verticalScalesRenderer.labelsColor = colorMode.chartLabelsColor + verticalScalesRenderer.axisXColor = colorMode.barChartStrongLinesColor + verticalScalesRenderer.horizontalLinesColor = colorMode.barChartStrongLinesColor + verticalLineRenderer.linesColor = colorMode.chartStrongLinesColor + } +} diff --git a/submodules/GraphCore/src/Charts/Controllers/Percent And Pie/PercentPieChartController.swift b/submodules/GraphCore/src/Charts/Controllers/Percent And Pie/PercentPieChartController.swift new file mode 100644 index 0000000000..3a8fc82be4 --- /dev/null +++ b/submodules/GraphCore/src/Charts/Controllers/Percent And Pie/PercentPieChartController.swift @@ -0,0 +1,291 @@ +// +// PercentPieChartController.swift +// GraphTest +// +// Created by Andrei Salavei on 4/7/19. +// Copyright © 2019 Andrei Salavei. All rights reserved. +// + +import Foundation +#if os(macOS) +import Cocoa +#else +import UIKit +#endif + +enum PercentConstants { + static let percentValueFormatter: NumberFormatter = { + let formatter = NumberFormatter() + formatter.positiveSuffix = "%" + return formatter + }() +} + +private enum Constants { + static let zoomedRange = 7 +} + +public class PercentPieChartController: BaseChartController { + let percentController: PercentChartComponentController + let pieController: PieChartComponentController + let transitionRenderer: PercentPieAnimationRenderer + + override public init(chartsCollection: ChartsCollection) { + transitionRenderer = PercentPieAnimationRenderer() + percentController = PercentChartComponentController(isZoomed: false, + mainPecentChartRenderer: PecentChartRenderer(), + horizontalScalesRenderer: HorizontalScalesRenderer(), + verticalScalesRenderer: VerticalScalesRenderer(), + verticalLineRenderer: VerticalLinesRenderer(), + previewPercentChartRenderer: PecentChartRenderer()) + pieController = PieChartComponentController(isZoomed: true, + pieChartRenderer: PieChartRenderer(), + previewBarChartRenderer: BarChartRenderer()) + + super.init(chartsCollection: chartsCollection) + + [percentController, pieController].forEach { controller in + controller.chartFrame = { [unowned self] in self.chartFrame() } + controller.cartViewBounds = { [unowned self] in self.cartViewBounds() } + controller.zoomInOnDateClosure = { [unowned self] date in + self.didTapZoomIn(date: date) + } + controller.setChartTitleClosure = { [unowned self] (title, animated) in + self.setChartTitleClosure?(title, animated) + } + controller.setDetailsViewPositionClosure = { [unowned self] (position) in + self.setDetailsViewPositionClosure?(position) + } + controller.setDetailsChartVisibleClosure = { [unowned self] (visible, animated) in + self.setDetailsChartVisibleClosure?(visible, animated) + } + controller.setDetailsViewModel = { [unowned self] (viewModel, animated) in + self.setDetailsViewModel?(viewModel, animated) + } + controller.updatePreviewRangeClosure = { [unowned self] (fraction, animated) in + self.chartRangeUpdatedClosure?(fraction, animated) + } + controller.chartRangePagingClosure = { [unowned self] (isEnabled, pageSize) in + self.setChartRangePagingEnabled(isEnabled: isEnabled, minimumSelectionSize: pageSize) + } + } + transitionRenderer.isEnabled = false + } + + public override var mainChartRenderers: [ChartViewRenderer] { + return [percentController.mainPecentChartRenderer, + transitionRenderer, + percentController.horizontalScalesRenderer, + percentController.verticalScalesRenderer, + percentController.verticalLineRenderer, + pieController.pieChartRenderer, +// performanceRenderer + ] + } + + public override var navigationRenderers: [ChartViewRenderer] { + return [percentController.previewPercentChartRenderer, + pieController.previewBarChartRenderer] + } + + public override func initializeChart() { + percentController.initialize(chartsCollection: initialChartsCollection, + initialDate: Date(), + totalHorizontalRange: BaseConstants.defaultRange, + totalVerticalRange: BaseConstants.defaultRange) + switchToChart(chartsCollection: percentController.chartsCollection, isZoomed: false, animated: false) + } + + func switchToChart(chartsCollection: ChartsCollection, isZoomed: Bool, animated: Bool) { + if animated { + TimeInterval.setDefaultSuration(.expandAnimationDuration) + DispatchQueue.main.asyncAfter(deadline: .now() + .expandAnimationDuration) { + TimeInterval.setDefaultSuration(.osXDuration) + } + } + + super.isZoomed = isZoomed + if isZoomed { + let toHorizontalRange = pieController.initialHorizontalRange + + pieController.updateChartsVisibility(visibility: percentController.chartVisibility, animated: false) + pieController.pieChartRenderer.setup(horizontalRange: percentController.currentHorizontalMainChartRange, animated: false) + pieController.previewBarChartRenderer.setup(horizontalRange: percentController.currentPreviewHorizontalRange, animated: false) + pieController.pieChartRenderer.setVisible(false, animated: false) + pieController.previewBarChartRenderer.setVisible(true, animated: false) + + pieController.willAppear(animated: animated) + percentController.willDisappear(animated: animated) + + pieController.pieChartRenderer.drawPie = false + percentController.mainPecentChartRenderer.isEnabled = false + + setupTransitionRenderer() + + percentController.setupMainChart(horizontalRange: toHorizontalRange, animated: animated) + percentController.previewPercentChartRenderer.setup(horizontalRange: toHorizontalRange, animated: animated) + percentController.setConponentsVisible(visible: false, animated: animated) + + transitionRenderer.animate(fromDataToPie: true, animated: animated) { [weak self] in + self?.pieController.pieChartRenderer.drawPie = true + self?.percentController.mainPecentChartRenderer.isEnabled = true + } + } else { + if !pieController.chartsCollection.isBlank { + let fromHorizontalRange = pieController.currentHorizontalMainChartRange + let toHorizontalRange = percentController.initialHorizontalRange + + pieController.pieChartRenderer.setup(horizontalRange: toHorizontalRange, animated: animated) + pieController.previewBarChartRenderer.setup(horizontalRange: toHorizontalRange, animated: animated) + pieController.pieChartRenderer.setVisible(false, animated: animated) + pieController.previewBarChartRenderer.setVisible(false, animated: animated) + + percentController.updateChartsVisibility(visibility: pieController.chartVisibility, animated: false) + percentController.setupMainChart(horizontalRange: fromHorizontalRange, animated: false) + percentController.previewPercentChartRenderer.setup(horizontalRange: fromHorizontalRange, animated: false) + percentController.setConponentsVisible(visible: false, animated: false) + } + + percentController.willAppear(animated: animated) + pieController.willDisappear(animated: animated) + + if animated { + pieController.pieChartRenderer.drawPie = false + percentController.mainPecentChartRenderer.isEnabled = false + + setupTransitionRenderer() + + transitionRenderer.animate(fromDataToPie: false, animated: true) { + self.pieController.pieChartRenderer.drawPie = true + self.percentController.mainPecentChartRenderer.isEnabled = true + } + } + } + + self.setBackButtonVisibilityClosure?(isZoomed, animated) + } + + func setupTransitionRenderer() { + transitionRenderer.setup(verticalRange: percentController.currentVerticalMainChartRange, animated: false) + transitionRenderer.setup(horizontalRange: percentController.currentHorizontalMainChartRange, animated: false) + transitionRenderer.visiblePieComponents = pieController.visiblePieDataWithCurrentPreviewRange + transitionRenderer.visiblePercentageData = percentController.currentlyVisiblePercentageData + } + + public override func updateChartsVisibility(visibility: [Bool], animated: Bool) { + if isZoomed { + pieController.updateChartsVisibility(visibility: visibility, animated: animated) + } else { + percentController.updateChartsVisibility(visibility: visibility, animated: animated) + } + } + + var visibleChartValues: [ChartsCollection.Chart] { + let visibility = isZoomed ? pieController.chartVisibility : percentController.chartVisibility + let collection = isZoomed ? pieController.chartsCollection : percentController.chartsCollection + let visibleCharts: [ChartsCollection.Chart] = visibility.enumerated().compactMap { args in + args.element ? collection.chartValues[args.offset] : nil + } + return visibleCharts + } + + public override var actualChartVisibility: [Bool] { + return isZoomed ? pieController.chartVisibility : percentController.chartVisibility + } + + public override var actualChartsCollection: ChartsCollection { + let collection = isZoomed ? pieController.chartsCollection : percentController.chartsCollection + + if collection.isBlank { + return self.initialChartsCollection + } + return collection + } + + public override func chartInteractionDidBegin(point: CGPoint) { + if isZoomed { + pieController.chartInteractionDidBegin(point: point) + } else { + percentController.chartInteractionDidBegin(point: point) + } + } + + public override func chartInteractionDidEnd() { + if isZoomed { + pieController.chartInteractionDidEnd() + } else { + percentController.chartInteractionDidEnd() + } + } + + public override var drawChartVisibity: Bool { + return true + } + + public override var currentChartHorizontalRangeFraction: ClosedRange { + if isZoomed { + return pieController.currentChartHorizontalRangeFraction + } else { + return percentController.currentChartHorizontalRangeFraction + } + } + + public override func cancelChartInteraction() { + if isZoomed { + return pieController.hideDetailsView(animated: true) + } else { + return percentController.hideDetailsView(animated: true) + } + } + + public override func didTapZoomIn(date: Date) { + guard isZoomed == false else { return } + cancelChartInteraction() + let currentCollection = percentController.chartsCollection + let range: Int = Constants.zoomedRange + guard let (foundDate, index) = percentController.findClosestDateTo(dateToFind: date) else { return } + var lowIndex = max(0, index - range / 2) + var highIndex = min(currentCollection.axisValues.count - 1, index + range / 2) + if lowIndex == 0 { + highIndex = lowIndex + (range - 1) + } else if highIndex == currentCollection.axisValues.count - 1 { + lowIndex = highIndex - (range - 1) + } + + let newValues = currentCollection.chartValues.map { chart in + return ChartsCollection.Chart(color: chart.color, + name: chart.name, + values: Array(chart.values[(lowIndex...highIndex)])) + } + let newCollection = ChartsCollection(axisValues: Array(currentCollection.axisValues[(lowIndex...highIndex)]), + chartValues: newValues) + let selectedRange = CGFloat(foundDate.timeIntervalSince1970 - .day)...CGFloat(foundDate.timeIntervalSince1970) + pieController.initialize(chartsCollection: newCollection, initialDate: date, totalHorizontalRange: 0...1, totalVerticalRange: 0...1) + pieController.initialHorizontalRange = selectedRange + + switchToChart(chartsCollection: newCollection, isZoomed: true, animated: true) + } + + public override func didTapZoomOut() { + self.pieController.deselectSegment(completion: { [weak self] in + guard let self = self else { return } + self.switchToChart(chartsCollection: self.percentController.chartsCollection, isZoomed: false, animated: true) + }) + } + + public override func updateChartRange(_ rangeFraction: ClosedRange) { + if isZoomed { + return pieController.chartRangeFractionDidUpdated(rangeFraction) + } else { + return percentController.chartRangeFractionDidUpdated(rangeFraction) + } + } + + public override func apply(colorMode: GColorMode, animated: Bool) { + super.apply(colorMode: colorMode, animated: animated) + + pieController.apply(colorMode: colorMode, animated: animated) + percentController.apply(colorMode: colorMode, animated: animated) + transitionRenderer.backgroundColor = colorMode.chartBackgroundColor + } +} diff --git a/submodules/GraphCore/src/Charts/Controllers/Percent And Pie/PieChartComponentController.swift b/submodules/GraphCore/src/Charts/Controllers/Percent And Pie/PieChartComponentController.swift new file mode 100644 index 0000000000..55cd1b710c --- /dev/null +++ b/submodules/GraphCore/src/Charts/Controllers/Percent And Pie/PieChartComponentController.swift @@ -0,0 +1,203 @@ +// +// PieChartComponentController.swift +// GraphTest +// +// Created by Andrei Salavei on 4/14/19. +// Copyright © 2019 Andrei Salavei. All rights reserved. +// + +import Foundation +#if os(macOS) +import Cocoa +#else +import UIKit +#endif + +class PieChartComponentController: GeneralChartComponentController { + let pieChartRenderer: PieChartRenderer + let previewBarChartRenderer: BarChartRenderer + var barWidth: CGFloat = 1 + + var chartBars: BarChartRenderer.BarsData = .blank + + init(isZoomed: Bool, + pieChartRenderer: PieChartRenderer, + previewBarChartRenderer: BarChartRenderer) { + self.pieChartRenderer = pieChartRenderer + self.previewBarChartRenderer = previewBarChartRenderer + super.init(isZoomed: isZoomed) + } + + override func initialize(chartsCollection: ChartsCollection, initialDate: Date, totalHorizontalRange _: ClosedRange, totalVerticalRange _: ClosedRange) { + let (width, chartBars, totalHorizontalRange, _) = BarChartRenderer.BarsData.initialComponents(chartsCollection: chartsCollection) + self.barWidth = width + self.chartBars = chartBars + super.initialize(chartsCollection: chartsCollection, + initialDate: initialDate, + totalHorizontalRange: totalHorizontalRange, + totalVerticalRange: BaseConstants.defaultRange) + + self.previewBarChartRenderer.bars = chartBars + self.previewBarChartRenderer.fillToTop = true + + pieChartRenderer.valuesFormatter = PercentConstants.percentValueFormatter + pieChartRenderer.setup(horizontalRange: initialHorizontalRange, animated: false) + previewBarChartRenderer.setup(verticalRange: initialVerticalRange, animated: false) + previewBarChartRenderer.setup(horizontalRange: initialHorizontalRange, animated: false) + + pieChartRenderer.updatePercentageData(pieDataWithCurrentPreviewRange, animated: false) + pieChartRenderer.selectSegmentAt(at: nil, animated: false) + } + + private var pieDataWithCurrentPreviewRange: [PieChartRenderer.PieComponent] { + let range = currentHorizontalMainChartRange + var pieComponents = chartsCollection.chartValues.map { PieChartRenderer.PieComponent(color: $0.color, + value: 0) } + guard var valueIndex = chartsCollection.axisValues.firstIndex(where: { CGFloat($0.timeIntervalSince1970) > (range.lowerBound + 1)}) else { + return pieComponents + } + var count = 0 + while valueIndex < chartsCollection.axisValues.count, CGFloat(chartsCollection.axisValues[valueIndex].timeIntervalSince1970) <= range.upperBound { + count += 1 + for pieIndex in pieComponents.indices { + pieComponents[pieIndex].value += CGFloat(chartsCollection.chartValues[pieIndex].values[valueIndex]) + } + valueIndex += 1 + } + return pieComponents + } + + var visiblePieDataWithCurrentPreviewRange: [PieChartRenderer.PieComponent] { + let currentData = pieDataWithCurrentPreviewRange + return chartVisibility.enumerated().compactMap { $0.element ? currentData[$0.offset] : nil } + } + + override func willAppear(animated: Bool) { + pieChartRenderer.setup(horizontalRange: initialHorizontalRange, animated: animated) + pieChartRenderer.setVisible(true, animated: animated) + + previewBarChartRenderer.setup(verticalRange: totalVerticalRange, animated: animated) + previewBarChartRenderer.setup(horizontalRange: totalHorizontalRange, animated: animated) + previewBarChartRenderer.setVisible(true, animated: animated) + + updatePreviewRangeClosure?(currentChartHorizontalRangeFraction, animated) + pieChartRenderer.updatePercentageData(pieDataWithCurrentPreviewRange, animated: false) + + super.willAppear(animated: animated) + } + + override func setupChartRangePaging() { + let valuesCount = chartsCollection.axisValues.count + guard valuesCount > 0 else { return } + chartRangePagingClosure?(true, 1.0 / CGFloat(valuesCount)) + } + + override func chartRangeDidUpdated(_ updatedRange: ClosedRange) { + if isChartInteractionBegun { + chartInteractionDidBegin(point: lastChartInteractionPoint) + } + initialHorizontalRange = updatedRange + + setupMainChart(horizontalRange: updatedRange, animated: true) + updateSelectedDataLabelIfNeeded() + } + + func setupMainChart(horizontalRange: ClosedRange, animated: Bool) { + pieChartRenderer.setup(horizontalRange: horizontalRange, animated: animated) + pieChartRenderer.updatePercentageData(pieDataWithCurrentPreviewRange, animated: animated) + } + + public override func updateChartsVisibility(visibility: [Bool], animated: Bool) { + super.updateChartsVisibility(visibility: visibility, animated: animated) + for (index, isVisible) in visibility.enumerated() { + pieChartRenderer.setComponentVisible(isVisible, at: index, animated: animated) + previewBarChartRenderer.setComponentVisible(isVisible, at: index, animated: animated) + } + if let segment = pieChartRenderer.selectedSegment { + if !visibility[segment] { + pieChartRenderer.selectSegmentAt(at: nil, animated: true) + } + } + updateSelectedDataLabelIfNeeded() + } + + func deselectSegment(completion: @escaping () -> Void) { + if pieChartRenderer.hasSelectedSegments { + hideDetailsView(animated: true) + pieChartRenderer.selectSegmentAt(at: nil, animated: true) + DispatchQueue.main.asyncAfter(deadline: .now() + .defaultDuration / 2) { + completion() + } + } else { + completion() + } + } + + func updateSelectedDataLabelIfNeeded() { + if let segment = pieChartRenderer.selectedSegment { + self.setDetailsChartVisibleClosure?(true, true) + self.setDetailsViewModel?(chartDetailsViewModel(segmentInde: segment), false) + self.setDetailsViewPositionClosure?(chartFrame().width / 4) + } else { + self.setDetailsChartVisibleClosure?(false, true) + } + } + + func chartDetailsViewModel(segmentInde: Int) -> ChartDetailsViewModel { + let pieItem = pieDataWithCurrentPreviewRange[segmentInde] + let title = chartsCollection.chartValues[segmentInde].name + let valueString = BaseConstants.detailsNumberFormatter.string(from: pieItem.value) + let viewModel = ChartDetailsViewModel(title: "", + showArrow: false, + showPrefixes: false, + values: [ChartDetailsViewModel.Value(prefix: nil, + title: title, + value: valueString, + color: pieItem.color, + visible: true)], + totalValue: nil, + tapAction: nil) + return viewModel + } + + override var currentMainRangeRenderer: BaseChartRenderer { + return pieChartRenderer + } + + override var currentPreviewRangeRenderer: BaseChartRenderer { + return previewBarChartRenderer + } + + var lastInteractionPoint: CGPoint = .zero + public override func chartInteractionDidBegin(point: CGPoint) { + lastInteractionPoint = point + } + + public override func chartInteractionDidEnd() { + if let segment = pieChartRenderer.selectedItemIndex(at: lastInteractionPoint) { + if pieChartRenderer.selectedSegment == segment { + pieChartRenderer.selectSegmentAt(at: nil, animated: true) + } else { + pieChartRenderer.selectSegmentAt(at: segment, animated: true) + } + updateSelectedDataLabelIfNeeded() + } + } + + override func hideDetailsView(animated: Bool) { + pieChartRenderer.selectSegmentAt(at: nil, animated: animated) + updateSelectedDataLabelIfNeeded() + } + + override func updateChartRangeTitle(animated: Bool) { + let fromDate = Date(timeIntervalSince1970: TimeInterval(currentHorizontalMainChartRange.lowerBound) + .day + 1) + let toDate = Date(timeIntervalSince1970: TimeInterval(currentHorizontalMainChartRange.upperBound)) + if Calendar.utc.startOfDay(for: fromDate) == Calendar.utc.startOfDay(for: toDate) { + let stirng = BaseConstants.headerFullZoomedFormatter.string(from: fromDate) + self.setChartTitleClosure?(stirng, animated) + } else { + let stirng = "\(BaseConstants.headerMediumRangeFormatter.string(from: fromDate)) - \(BaseConstants.headerMediumRangeFormatter.string(from: toDate))" + self.setChartTitleClosure?(stirng, animated) + } + } +} diff --git a/submodules/GraphCore/src/Charts/Controllers/Stacked Bars/BarsComponentController.swift b/submodules/GraphCore/src/Charts/Controllers/Stacked Bars/BarsComponentController.swift new file mode 100644 index 0000000000..c0193ecbdf --- /dev/null +++ b/submodules/GraphCore/src/Charts/Controllers/Stacked Bars/BarsComponentController.swift @@ -0,0 +1,231 @@ +// +// BarsComponentController.swift +// GraphTest +// +// Created by Andrei Salavei on 4/14/19. +// Copyright © 2019 Andrei Salavei. All rights reserved. +// + +import Foundation +#if os(macOS) +import Cocoa +#else +import UIKit +#endif + +class BarsComponentController: GeneralChartComponentController { + let mainBarsRenderer: BarChartRenderer + let horizontalScalesRenderer: HorizontalScalesRenderer + let verticalScalesRenderer: VerticalScalesRenderer + + let previewBarsChartRenderer: BarChartRenderer + private(set) var barsWidth: CGFloat = 1 + + private (set) var chartBars: BarChartRenderer.BarsData = .blank + + init(isZoomed: Bool, + mainBarsRenderer: BarChartRenderer, + horizontalScalesRenderer: HorizontalScalesRenderer, + verticalScalesRenderer: VerticalScalesRenderer, + previewBarsChartRenderer: BarChartRenderer) { + self.mainBarsRenderer = mainBarsRenderer + self.horizontalScalesRenderer = horizontalScalesRenderer + self.verticalScalesRenderer = verticalScalesRenderer + self.previewBarsChartRenderer = previewBarsChartRenderer + + self.mainBarsRenderer.optimizationLevel = BaseConstants.barsChartOptimizationLevel + self.previewBarsChartRenderer.optimizationLevel = BaseConstants.barsChartOptimizationLevel + + super.init(isZoomed: isZoomed) + } + + override func initialize(chartsCollection: ChartsCollection, initialDate: Date, totalHorizontalRange _: ClosedRange, totalVerticalRange _: ClosedRange) { + let (width, chartBars, totalHorizontalRange, totalVerticalRange) = BarChartRenderer.BarsData.initialComponents(chartsCollection: chartsCollection) + self.chartBars = chartBars + self.barsWidth = width + + super.initialize(chartsCollection: chartsCollection, + initialDate: initialDate, + totalHorizontalRange: totalHorizontalRange, + totalVerticalRange: totalVerticalRange) + } + + override func setupInitialChartRange(initialDate: Date) { + guard let first = chartsCollection.axisValues.first?.timeIntervalSince1970, + let last = chartsCollection.axisValues.last?.timeIntervalSince1970 else { return } + + let rangeStart = CGFloat(first) + let rangeEnd = CGFloat(last) + + if isZoomed { + let initalDate = CGFloat(initialDate.timeIntervalSince1970) + + initialHorizontalRange = max(initalDate - barsWidth, rangeStart)...min(initalDate + GeneralChartComponentConstants.defaultZoomedRangeLength - barsWidth, rangeEnd) + initialVerticalRange = totalVerticalRange + } else { + super.setupInitialChartRange(initialDate: initialDate) + } + } + + + override func willAppear(animated: Bool) { + mainBarsRenderer.bars = self.chartBars + previewBarsChartRenderer.bars = self.chartBars + + previewBarsChartRenderer.setup(verticalRange: totalVerticalRange, animated: animated) + previewBarsChartRenderer.setup(horizontalRange: totalHorizontalRange, animated: animated) + + setupMainChart(verticalRange: initialVerticalRange, animated: animated) + setupMainChart(horizontalRange: initialHorizontalRange, animated: animated) + + updateChartVerticalRanges(horizontalRange: initialHorizontalRange, animated: animated) + + super.willAppear(animated: animated) + + updatePreviewRangeClosure?(currentChartHorizontalRangeFraction, animated) + setConponentsVisible(visible: true, animated: animated) + updateHorizontalLimitLabels(animated: animated, forceUpdate: true) + } + + override func chartRangeDidUpdated(_ updatedRange: ClosedRange) { + super.chartRangeDidUpdated(updatedRange) + if !isZoomed { + initialHorizontalRange = updatedRange + } + setupMainChart(horizontalRange: updatedRange, animated: false) + updateHorizontalLimitLabels(animated: true, forceUpdate: false) + updateChartVerticalRanges(horizontalRange: updatedRange, animated: true) + } + + func updateHorizontalLimitLabels(animated: Bool, forceUpdate: Bool) { + updateHorizontalLimitLabels(horizontalScalesRenderer: horizontalScalesRenderer, + horizontalRange: currentHorizontalMainChartRange, + scaleType: isZoomed ? .hour : .day, + forceUpdate: forceUpdate, + animated: animated) + } + + func prepareAppearanceAnimation(horizontalRnage: ClosedRange) { + setupMainChart(horizontalRange: horizontalRnage, animated: false) + setConponentsVisible(visible: false, animated: false) + } + + func setConponentsVisible(visible: Bool, animated: Bool) { + mainBarsRenderer.setVisible(visible, animated: animated) + horizontalScalesRenderer.setVisible(visible, animated: animated) + verticalScalesRenderer.setVisible(visible, animated: animated) + previewBarsChartRenderer.setVisible(visible, animated: animated) + } + + func setupMainChart(horizontalRange: ClosedRange, animated: Bool) { + mainBarsRenderer.setup(horizontalRange: horizontalRange, animated: animated) + horizontalScalesRenderer.setup(horizontalRange: horizontalRange, animated: animated) + verticalScalesRenderer.setup(horizontalRange: horizontalRange, animated: animated) + } + + var visibleBars: BarChartRenderer.BarsData { + let visibleComponents: [BarChartRenderer.BarsData.Component] = chartVisibility.enumerated().compactMap { args in + args.element ? chartBars.components[args.offset] : nil + } + return BarChartRenderer.BarsData(barWidth: chartBars.barWidth, + locations: chartBars.locations, + components: visibleComponents) + } + + func updateChartVerticalRanges(horizontalRange: ClosedRange, animated: Bool) { + if let range = BarChartRenderer.BarsData.verticalRange(bars: visibleBars, + calculatingRange: horizontalRange, + addBounds: true) { + let (range, labels) = verticalLimitsLabels(verticalRange: range) + if verticalScalesRenderer.verticalRange.end != range { + verticalScalesRenderer.setup(verticalLimitsLabels: labels, animated: animated) + } + verticalScalesRenderer.setVisible(true, animated: animated) + + setupMainChart(verticalRange: range, animated: animated) + } else { + verticalScalesRenderer.setVisible(false, animated: animated) + } + + if let range = BarChartRenderer.BarsData.verticalRange(bars: visibleBars) { + previewBarsChartRenderer.setup(verticalRange: range, animated: animated) + } + } + + func setupMainChart(verticalRange: ClosedRange, animated: Bool) { + mainBarsRenderer.setup(verticalRange: verticalRange, animated: animated) + horizontalScalesRenderer.setup(verticalRange: verticalRange, animated: animated) + verticalScalesRenderer.setup(verticalRange: verticalRange, animated: animated) + } + + public override func updateChartsVisibility(visibility: [Bool], animated: Bool) { + super.updateChartsVisibility(visibility: visibility, animated: animated) + for (index, isVisible) in visibility.enumerated() { + mainBarsRenderer.setComponentVisible(isVisible, at: index, animated: animated) + previewBarsChartRenderer.setComponentVisible(isVisible, at: index, animated: animated) + } + updateChartVerticalRanges(horizontalRange: currentHorizontalMainChartRange, animated: true) + } + + var visibleChartValues: [ChartsCollection.Chart] { + let visibleCharts: [ChartsCollection.Chart] = chartVisibility.enumerated().compactMap { args in + args.element ? chartsCollection.chartValues[args.offset] : nil + } + return visibleCharts + } + + override func chartDetailsViewModel(closestDate: Date, pointIndex: Int) -> ChartDetailsViewModel { + var viewModel = super.chartDetailsViewModel(closestDate: closestDate, pointIndex: pointIndex) + let visibleChartValues = self.visibleChartValues + let totalSumm: CGFloat = visibleChartValues.map { CGFloat($0.values[pointIndex]) }.reduce(0, +) + + viewModel.totalValue = ChartDetailsViewModel.Value(prefix: nil, + title: "Total", + value: BaseConstants.detailsNumberFormatter.string(from: totalSumm), + color: .white, + visible: visibleChartValues.count > 1) + return viewModel + } + + override var currentMainRangeRenderer: BaseChartRenderer { + return mainBarsRenderer + } + + override var currentPreviewRangeRenderer: BaseChartRenderer { + return previewBarsChartRenderer + } + + override func showDetailsView(at chartPosition: CGFloat, detailsViewPosition: CGFloat, dataIndex: Int, date: Date, animted: Bool) { + let rangeWithOffset = detailsViewPosition - barsWidth / currentHorizontalMainChartRange.distance * chartFrame().width / 2 + super.showDetailsView(at: chartPosition, detailsViewPosition: rangeWithOffset, dataIndex: dataIndex, date: date, animted: animted) + mainBarsRenderer.setSelectedIndex(dataIndex, animated: true) + } + + override func hideDetailsView(animated: Bool) { + super.hideDetailsView(animated: animated) + + mainBarsRenderer.setSelectedIndex(nil, animated: animated) + } + override func apply(colorMode: GColorMode, animated: Bool) { + super.apply(colorMode: colorMode, animated: animated) + + horizontalScalesRenderer.labelsColor = colorMode.chartLabelsColor + verticalScalesRenderer.labelsColor = colorMode.chartLabelsColor + verticalScalesRenderer.axisXColor = colorMode.barChartStrongLinesColor + verticalScalesRenderer.horizontalLinesColor = colorMode.barChartStrongLinesColor + mainBarsRenderer.update(backgroundColor: colorMode.chartBackgroundColor, animated: false) + previewBarsChartRenderer.update(backgroundColor: colorMode.chartBackgroundColor, animated: false) + } + + override func updateChartRangeTitle(animated: Bool) { + let fromDate = Date(timeIntervalSince1970: TimeInterval(currentHorizontalMainChartRange.lowerBound + barsWidth)) + let toDate = Date(timeIntervalSince1970: TimeInterval(currentHorizontalMainChartRange.upperBound)) + if Calendar.utc.startOfDay(for: fromDate) == Calendar.utc.startOfDay(for: toDate) { + let stirng = BaseConstants.headerFullZoomedFormatter.string(from: fromDate) + self.setChartTitleClosure?(stirng, animated) + } else { + let stirng = "\(BaseConstants.headerMediumRangeFormatter.string(from: fromDate)) - \(BaseConstants.headerMediumRangeFormatter.string(from: toDate))" + self.setChartTitleClosure?(stirng, animated) + } + } +} diff --git a/submodules/GraphCore/src/Charts/Controllers/Stacked Bars/DailyBarsChartController.swift b/submodules/GraphCore/src/Charts/Controllers/Stacked Bars/DailyBarsChartController.swift new file mode 100644 index 0000000000..50da946780 --- /dev/null +++ b/submodules/GraphCore/src/Charts/Controllers/Stacked Bars/DailyBarsChartController.swift @@ -0,0 +1,259 @@ +// +// DailyBarsChartController.swift +// GraphTest +// +// Created by Andrei Salavei on 4/7/19. +// Copyright © 2019 Andrei Salavei. All rights reserved. +// + +import Foundation +#if os(macOS) +import Cocoa +#else +import UIKit +#endif + +public class DailyBarsChartController: BaseChartController { + let barsController: BarsComponentController + let linesController: LinesComponentController + + override public init(chartsCollection: ChartsCollection) { + let horizontalScalesRenderer = HorizontalScalesRenderer() + let verticalScalesRenderer = VerticalScalesRenderer() + barsController = BarsComponentController(isZoomed: false, + mainBarsRenderer: BarChartRenderer(), + horizontalScalesRenderer: horizontalScalesRenderer, + verticalScalesRenderer: verticalScalesRenderer, + previewBarsChartRenderer: BarChartRenderer()) + linesController = LinesComponentController(isZoomed: true, + userLinesTransitionAnimation: false, + mainLinesRenderer: LinesChartRenderer(), + horizontalScalesRenderer: horizontalScalesRenderer, + verticalScalesRenderer: verticalScalesRenderer, + verticalLineRenderer: VerticalLinesRenderer(), + lineBulletsRenerer: LineBulletsRenerer(), + previewLinesChartRenderer: LinesChartRenderer()) + + super.init(chartsCollection: chartsCollection) + + [barsController, linesController].forEach { controller in + controller.chartFrame = { [unowned self] in self.chartFrame() } + controller.cartViewBounds = { [unowned self] in self.cartViewBounds() } + controller.zoomInOnDateClosure = { [unowned self] date in + self.didTapZoomIn(date: date) + } + controller.setChartTitleClosure = { [unowned self] (title, animated) in + self.setChartTitleClosure?(title, animated) + } + controller.setDetailsViewPositionClosure = { [unowned self] (position) in + self.setDetailsViewPositionClosure?(position) + } + controller.setDetailsChartVisibleClosure = { [unowned self] (visible, animated) in + self.setDetailsChartVisibleClosure?(visible, animated) + } + controller.setDetailsViewModel = { [unowned self] (viewModel, animated) in + self.setDetailsViewModel?(viewModel, animated) + } + controller.updatePreviewRangeClosure = { [unowned self] (fraction, animated) in + self.chartRangeUpdatedClosure?(fraction, animated) + } + controller.chartRangePagingClosure = { [unowned self] (isEnabled, pageSize) in + self.setChartRangePagingEnabled(isEnabled: isEnabled, minimumSelectionSize: pageSize) + } + } + } + + public override var mainChartRenderers: [ChartViewRenderer] { + return [barsController.mainBarsRenderer, + linesController.mainLinesRenderer, + barsController.horizontalScalesRenderer, + barsController.verticalScalesRenderer, + linesController.verticalLineRenderer, + linesController.lineBulletsRenerer, +// performanceRenderer + ] + } + + public override var navigationRenderers: [ChartViewRenderer] { + return [barsController.previewBarsChartRenderer, + linesController.previewLinesChartRenderer] + } + + public override func initializeChart() { + barsController.initialize(chartsCollection: initialChartsCollection, + initialDate: Date(), + totalHorizontalRange: BaseConstants.defaultRange, + totalVerticalRange: BaseConstants.defaultRange) + switchToChart(chartsCollection: barsController.chartsCollection, isZoomed: false, animated: false) + } + + func switchToChart(chartsCollection: ChartsCollection, isZoomed: Bool, animated: Bool) { + if animated { + TimeInterval.setDefaultSuration(.expandAnimationDuration) + DispatchQueue.main.asyncAfter(deadline: .now() + .expandAnimationDuration) { + TimeInterval.setDefaultSuration(.osXDuration) + } + } + + super.isZoomed = isZoomed + if isZoomed { + let toHorizontalRange = linesController.initialHorizontalRange + let destinationHorizontalRange = (toHorizontalRange.lowerBound - barsController.barsWidth)...(toHorizontalRange.upperBound - barsController.barsWidth) + let initialChartVerticalRange = lineProportionAnimationRange() + + linesController.mainLinesRenderer.setup(horizontalRange: barsController.currentHorizontalMainChartRange, animated: false) + linesController.previewLinesChartRenderer.setup(horizontalRange: barsController.currentPreviewHorizontalRange, animated: false) + linesController.mainLinesRenderer.setup(verticalRange: initialChartVerticalRange, animated: false) + linesController.previewLinesChartRenderer.setup(verticalRange: initialChartVerticalRange, animated: false) + linesController.mainLinesRenderer.setVisible(false, animated: false) + linesController.previewLinesChartRenderer.setVisible(false, animated: false) + + barsController.setupMainChart(horizontalRange: destinationHorizontalRange, animated: animated) + barsController.previewBarsChartRenderer.setup(horizontalRange: linesController.totalHorizontalRange, animated: animated) + barsController.mainBarsRenderer.setVisible(false, animated: animated) + barsController.previewBarsChartRenderer.setVisible(false, animated: animated) + + linesController.willAppear(animated: animated) + barsController.willDisappear(animated: animated) + + linesController.updateChartsVisibility(visibility: linesController.chartLines.map { _ in true }, animated: false) + } else { + if !linesController.chartsCollection.isBlank { + barsController.hideDetailsView(animated: false) + let visibleVerticalRange = BarChartRenderer.BarsData.verticalRange(bars: barsController.visibleBars, + calculatingRange: barsController.initialHorizontalRange) ?? BaseConstants.defaultRange + barsController.mainBarsRenderer.setup(verticalRange: visibleVerticalRange, animated: false) + + let toHorizontalRange = barsController.initialHorizontalRange + let destinationChartVerticalRange = lineProportionAnimationRange() + + linesController.setupMainChart(horizontalRange: toHorizontalRange, animated: animated) + linesController.mainLinesRenderer.setup(verticalRange: destinationChartVerticalRange, animated: animated) + linesController.previewLinesChartRenderer.setup(verticalRange: destinationChartVerticalRange, animated: animated) + linesController.previewLinesChartRenderer.setup(horizontalRange: barsController.totalHorizontalRange, animated: animated) + linesController.mainLinesRenderer.setVisible(false, animated: animated) + linesController.previewLinesChartRenderer.setVisible(false, animated: animated) + } + + barsController.willAppear(animated: animated) + linesController.willDisappear(animated: animated) + } + + self.setBackButtonVisibilityClosure?(isZoomed, animated) + self.refreshChartToolsClosure?(animated) + } + + public override func updateChartsVisibility(visibility: [Bool], animated: Bool) { + if isZoomed { + linesController.updateChartsVisibility(visibility: visibility, animated: animated) + } else { + barsController.updateChartsVisibility(visibility: visibility, animated: animated) + } + } + + var visibleChartValues: [ChartsCollection.Chart] { + let visibility = isZoomed ? linesController.chartVisibility : barsController.chartVisibility + let collection = isZoomed ? linesController.chartsCollection : barsController.chartsCollection + let visibleCharts: [ChartsCollection.Chart] = visibility.enumerated().compactMap { args in + args.element ? collection.chartValues[args.offset] : nil + } + return visibleCharts + } + + public override var actualChartVisibility: [Bool] { + return isZoomed ? linesController.chartVisibility : barsController.chartVisibility + } + + public override var actualChartsCollection: ChartsCollection { + let collection = isZoomed ? linesController.chartsCollection : barsController.chartsCollection + + if collection.isBlank { + return self.initialChartsCollection + } + return collection + } + + public override func chartInteractionDidBegin(point: CGPoint) { + if isZoomed { + linesController.chartInteractionDidBegin(point: point) + } else { + barsController.chartInteractionDidBegin(point: point) + } + } + + public override func chartInteractionDidEnd() { + if isZoomed { + linesController.chartInteractionDidEnd() + } else { + barsController.chartInteractionDidEnd() + } + } + + public override var currentChartHorizontalRangeFraction: ClosedRange { + if isZoomed { + return linesController.currentChartHorizontalRangeFraction + } else { + return barsController.currentChartHorizontalRangeFraction + } + } + + public override func cancelChartInteraction() { + if isZoomed { + return linesController.hideDetailsView(animated: true) + } else { + return barsController.hideDetailsView(animated: true) + } + } + + public override func didTapZoomIn(date: Date) { + guard isZoomed == false else { return } + if isZoomed { + return linesController.hideDetailsView(animated: true) + } + self.getDetailsData?(date, { updatedCollection in + if let updatedCollection = updatedCollection { + self.linesController.initialize(chartsCollection: updatedCollection, + initialDate: date, + totalHorizontalRange: 0...1, + totalVerticalRange: 0...1) + self.switchToChart(chartsCollection: updatedCollection, isZoomed: true, animated: true) + } + }) + } + + func lineProportionAnimationRange() -> ClosedRange { + let visibleLines = self.barsController.chartVisibility.enumerated().compactMap { $0.element ? self.linesController.chartLines[$0.offset] : nil } + let linesRange = LinesChartRenderer.LineData.verticalRange(lines: visibleLines) ?? BaseConstants.defaultRange + let barsRange = BarChartRenderer.BarsData.verticalRange(bars: self.barsController.visibleBars, + calculatingRange: self.linesController.totalHorizontalRange) ?? BaseConstants.defaultRange + let range = 0...(linesRange.upperBound / barsRange.distance * self.barsController.currentVerticalMainChartRange.distance) + return range + } + + public override func didTapZoomOut() { + cancelChartInteraction() + switchToChart(chartsCollection: barsController.chartsCollection, isZoomed: false, animated: true) + } + + public override func updateChartRange(_ rangeFraction: ClosedRange) { + if isZoomed { + return linesController.chartRangeFractionDidUpdated(rangeFraction) + } else { + return barsController.chartRangeFractionDidUpdated(rangeFraction) + } + } + + override public func apply(colorMode: GColorMode, animated: Bool) { + super.apply(colorMode: colorMode, animated: animated) + + linesController.apply(colorMode: colorMode, animated: animated) + barsController.apply(colorMode: colorMode, animated: animated) + } + + public override var drawChartVisibity: Bool { + return true + } +} + +//TODO: Убрать Performance полоски сверзу чартов (Не забыть) +//TODO: Добавить ховеры на кнопки diff --git a/submodules/GraphCore/src/Charts/Controllers/Stacked Bars/LinesComponentController.swift b/submodules/GraphCore/src/Charts/Controllers/Stacked Bars/LinesComponentController.swift new file mode 100644 index 0000000000..42abe3494c --- /dev/null +++ b/submodules/GraphCore/src/Charts/Controllers/Stacked Bars/LinesComponentController.swift @@ -0,0 +1,215 @@ +// +// LinesComponentController.swift +// GraphTest +// +// Created by Andrei Salavei on 4/14/19. +// Copyright © 2019 Andrei Salavei. All rights reserved. +// + +import Foundation +#if os(macOS) +import Cocoa +#else +import UIKit +#endif + +class LinesComponentController: GeneralChartComponentController { + let mainLinesRenderer: LinesChartRenderer + let horizontalScalesRenderer: HorizontalScalesRenderer + let verticalScalesRenderer: VerticalScalesRenderer + let verticalLineRenderer: VerticalLinesRenderer + let lineBulletsRenerer: LineBulletsRenerer + + let previewLinesChartRenderer: LinesChartRenderer + + private let zoomedLinesRenderer = LinesChartRenderer() + private let zoomedPreviewLinesRenderer = LinesChartRenderer() + + private let userLinesTransitionAnimation: Bool + + private(set) var chartLines: [LinesChartRenderer.LineData] = [] + + init(isZoomed: Bool, + userLinesTransitionAnimation: Bool, + mainLinesRenderer: LinesChartRenderer, + horizontalScalesRenderer: HorizontalScalesRenderer, + verticalScalesRenderer: VerticalScalesRenderer, + verticalLineRenderer: VerticalLinesRenderer, + lineBulletsRenerer: LineBulletsRenerer, + previewLinesChartRenderer: LinesChartRenderer) { + self.mainLinesRenderer = mainLinesRenderer + self.horizontalScalesRenderer = horizontalScalesRenderer + self.verticalScalesRenderer = verticalScalesRenderer + self.verticalLineRenderer = verticalLineRenderer + self.lineBulletsRenerer = lineBulletsRenerer + self.previewLinesChartRenderer = previewLinesChartRenderer + self.userLinesTransitionAnimation = userLinesTransitionAnimation + + super.init(isZoomed: isZoomed) + + self.mainLinesRenderer.lineWidth = BaseConstants.mainChartLineWidth + self.mainLinesRenderer.optimizationLevel = BaseConstants.linesChartOptimizationLevel + self.previewLinesChartRenderer.lineWidth = BaseConstants.previewChartLineWidth + self.previewLinesChartRenderer.optimizationLevel = BaseConstants.previewLinesChartOptimizationLevel + + self.lineBulletsRenerer.isEnabled = false + } + + override func initialize(chartsCollection: ChartsCollection, + initialDate: Date, + totalHorizontalRange _: ClosedRange, + totalVerticalRange _: ClosedRange) { + let (chartLines, totalHorizontalRange, totalVerticalRange) = LinesChartRenderer.LineData.initialComponents(chartsCollection: chartsCollection) + self.chartLines = chartLines + + self.lineBulletsRenerer.bullets = self.chartLines.map { LineBulletsRenerer.Bullet(coordinate: $0.points.first ?? .zero, + color: $0.color)} + + super.initialize(chartsCollection: chartsCollection, + initialDate: initialDate, + totalHorizontalRange: totalHorizontalRange, + totalVerticalRange: totalVerticalRange) + + self.mainLinesRenderer.setup(verticalRange: totalVerticalRange, animated: true) + } + + override func willAppear(animated: Bool) { + mainLinesRenderer.setLines(lines: self.chartLines, animated: animated && userLinesTransitionAnimation) + previewLinesChartRenderer.setLines(lines: self.chartLines, animated: animated && userLinesTransitionAnimation) + + previewLinesChartRenderer.setup(verticalRange: totalVerticalRange, animated: animated) + previewLinesChartRenderer.setup(horizontalRange: totalHorizontalRange, animated: animated) + + setupMainChart(verticalRange: initialVerticalRange, animated: animated) + setupMainChart(horizontalRange: initialHorizontalRange, animated: animated) + + updateChartVerticalRanges(horizontalRange: initialHorizontalRange, animated: animated) + + super.willAppear(animated: animated) + + updatePreviewRangeClosure?(currentChartHorizontalRangeFraction, animated) + setConponentsVisible(visible: true, animated: animated) + updateHorizontalLimitLabels(animated: animated, forceUpdate: true) + } + + override func chartRangeDidUpdated(_ updatedRange: ClosedRange) { + super.chartRangeDidUpdated(updatedRange) + if !isZoomed { + initialHorizontalRange = updatedRange + } + setupMainChart(horizontalRange: updatedRange, animated: false) + updateHorizontalLimitLabels(animated: true, forceUpdate: false) + updateChartVerticalRanges(horizontalRange: updatedRange, animated: true) + } + + func updateHorizontalLimitLabels(animated: Bool, forceUpdate: Bool) { + updateHorizontalLimitLabels(horizontalScalesRenderer: horizontalScalesRenderer, + horizontalRange: currentHorizontalMainChartRange, + scaleType: isZoomed ? .hour : .day, + forceUpdate: forceUpdate, + animated: animated) + } + + func prepareAppearanceAnimation(horizontalRnage: ClosedRange) { + setupMainChart(horizontalRange: horizontalRnage, animated: false) + setConponentsVisible(visible: false, animated: false) + } + + func setConponentsVisible(visible: Bool, animated: Bool) { + mainLinesRenderer.setVisible(visible, animated: animated) + horizontalScalesRenderer.setVisible(visible, animated: animated) + verticalScalesRenderer.setVisible(visible, animated: animated) + verticalLineRenderer.setVisible(visible, animated: animated) + previewLinesChartRenderer.setVisible(visible, animated: animated) + lineBulletsRenerer.setVisible(visible, animated: animated) + } + + func setupMainChart(horizontalRange: ClosedRange, animated: Bool) { + mainLinesRenderer.setup(horizontalRange: horizontalRange, animated: animated) + horizontalScalesRenderer.setup(horizontalRange: horizontalRange, animated: animated) + verticalScalesRenderer.setup(horizontalRange: horizontalRange, animated: animated) + verticalLineRenderer.setup(horizontalRange: horizontalRange, animated: animated) + lineBulletsRenerer.setup(horizontalRange: horizontalRange, animated: animated) + } + + var visibleLines: [LinesChartRenderer.LineData] { + return chartVisibility.enumerated().compactMap { $0.element ? chartLines[$0.offset] : nil } + } + + func updateChartVerticalRanges(horizontalRange: ClosedRange, animated: Bool) { + if let range = LinesChartRenderer.LineData.verticalRange(lines: visibleLines, + calculatingRange: horizontalRange, + addBounds: true) { + let (range, labels) = verticalLimitsLabels(verticalRange: range) + if verticalScalesRenderer.verticalRange.end != range { + verticalScalesRenderer.setup(verticalLimitsLabels: labels, animated: animated) + } + + setupMainChart(verticalRange: range, animated: animated) + verticalScalesRenderer.setVisible(true, animated: animated) + } else { + verticalScalesRenderer.setVisible(false, animated: animated) + } + + if let range = LinesChartRenderer.LineData.verticalRange(lines: visibleLines) { + previewLinesChartRenderer.setup(verticalRange: range, animated: animated) + } + } + + func setupMainChart(verticalRange: ClosedRange, animated: Bool) { + mainLinesRenderer.setup(verticalRange: verticalRange, animated: animated) + horizontalScalesRenderer.setup(verticalRange: verticalRange, animated: animated) + verticalScalesRenderer.setup(verticalRange: verticalRange, animated: animated) + verticalLineRenderer.setup(verticalRange: verticalRange, animated: animated) + lineBulletsRenerer.setup(verticalRange: verticalRange, animated: animated) + } + + public override func updateChartsVisibility(visibility: [Bool], animated: Bool) { + super.updateChartsVisibility(visibility: visibility, animated: animated) + for (index, isVisible) in visibility.enumerated() { + mainLinesRenderer.setLineVisible(isVisible, at: index, animated: animated) + previewLinesChartRenderer.setLineVisible(isVisible, at: index, animated: animated) + lineBulletsRenerer.setLineVisible(isVisible, at: index, animated: animated) + } + updateChartVerticalRanges(horizontalRange: currentHorizontalMainChartRange, animated: true) + } + + override var currentMainRangeRenderer: BaseChartRenderer { + return mainLinesRenderer + } + + override var currentPreviewRangeRenderer: BaseChartRenderer { + return previewLinesChartRenderer + } + + override func showDetailsView(at chartPosition: CGFloat, detailsViewPosition: CGFloat, dataIndex: Int, date: Date, animted: Bool) { + super.showDetailsView(at: chartPosition, detailsViewPosition: detailsViewPosition, dataIndex: dataIndex, date: date, animted: animted) + verticalLineRenderer.values = [chartPosition] + verticalLineRenderer.isEnabled = true + + lineBulletsRenerer.isEnabled = true + lineBulletsRenerer.setVisible(true, animated: animted) + lineBulletsRenerer.bullets = chartLines.compactMap { chart in + return LineBulletsRenerer.Bullet(coordinate: chart.points[dataIndex], color: chart.color) + } + } + + override func hideDetailsView(animated: Bool) { + super.hideDetailsView(animated: animated) + + verticalLineRenderer.values = [] + verticalLineRenderer.isEnabled = false + lineBulletsRenerer.isEnabled = false + } + + override func apply(colorMode: GColorMode, animated: Bool) { + super.apply(colorMode: colorMode, animated: animated) + + horizontalScalesRenderer.labelsColor = colorMode.chartLabelsColor + verticalScalesRenderer.labelsColor = colorMode.chartLabelsColor + verticalScalesRenderer.axisXColor = colorMode.chartStrongLinesColor + verticalScalesRenderer.horizontalLinesColor = colorMode.chartHelperLinesColor + lineBulletsRenerer.setInnerColor(colorMode.chartBackgroundColor, animated: animated) + verticalLineRenderer.linesColor = colorMode.chartStrongLinesColor + } +} diff --git a/submodules/GraphCore/src/Charts/Controllers/Stacked Bars/StackedBarsChartController.swift b/submodules/GraphCore/src/Charts/Controllers/Stacked Bars/StackedBarsChartController.swift new file mode 100644 index 0000000000..bf8056e41b --- /dev/null +++ b/submodules/GraphCore/src/Charts/Controllers/Stacked Bars/StackedBarsChartController.swift @@ -0,0 +1,252 @@ +// +// StackedBarsChartController.swift +// GraphTest +// +// Created by Andrei Salavei on 4/7/19. +// Copyright © 2019 Andrei Salavei. All rights reserved. +// + +import Foundation +#if os(macOS) +import Cocoa +#else +import UIKit +#endif + +public class StackedBarsChartController: BaseChartController { + let barsController: BarsComponentController + let zoomedBarsController: BarsComponentController + + override public init(chartsCollection: ChartsCollection) { + let horizontalScalesRenderer = HorizontalScalesRenderer() + let verticalScalesRenderer = VerticalScalesRenderer() + barsController = BarsComponentController(isZoomed: false, + mainBarsRenderer: BarChartRenderer(), + horizontalScalesRenderer: horizontalScalesRenderer, + verticalScalesRenderer: verticalScalesRenderer, + previewBarsChartRenderer: BarChartRenderer()) + zoomedBarsController = BarsComponentController(isZoomed: true, + mainBarsRenderer: BarChartRenderer(), + horizontalScalesRenderer: horizontalScalesRenderer, + verticalScalesRenderer: verticalScalesRenderer, + previewBarsChartRenderer: BarChartRenderer()) + + super.init(chartsCollection: chartsCollection) + + [barsController, zoomedBarsController].forEach { controller in + controller.chartFrame = { [unowned self] in self.chartFrame() } + controller.cartViewBounds = { [unowned self] in self.cartViewBounds() } + controller.zoomInOnDateClosure = { [unowned self] date in + self.didTapZoomIn(date: date) + } + controller.setChartTitleClosure = { [unowned self] (title, animated) in + self.setChartTitleClosure?(title, animated) + } + controller.setDetailsViewPositionClosure = { [unowned self] (position) in + self.setDetailsViewPositionClosure?(position) + } + controller.setDetailsChartVisibleClosure = { [unowned self] (visible, animated) in + self.setDetailsChartVisibleClosure?(visible, animated) + } + controller.setDetailsViewModel = { [unowned self] (viewModel, animated) in + self.setDetailsViewModel?(viewModel, animated) + } + controller.updatePreviewRangeClosure = { [unowned self] (fraction, animated) in + self.chartRangeUpdatedClosure?(fraction, animated) + } + controller.chartRangePagingClosure = { [unowned self] (isEnabled, pageSize) in + self.setChartRangePagingEnabled(isEnabled: isEnabled, minimumSelectionSize: pageSize) + } + } + } + + public override var mainChartRenderers: [ChartViewRenderer] { + return [barsController.mainBarsRenderer, + zoomedBarsController.mainBarsRenderer, + barsController.horizontalScalesRenderer, + barsController.verticalScalesRenderer, +// performanceRenderer + ] + } + + public override var navigationRenderers: [ChartViewRenderer] { + return [barsController.previewBarsChartRenderer, + zoomedBarsController.previewBarsChartRenderer] + } + + public override func initializeChart() { + barsController.initialize(chartsCollection: initialChartsCollection, + initialDate: Date(), + totalHorizontalRange: BaseConstants.defaultRange, + totalVerticalRange: BaseConstants.defaultRange) + switchToChart(chartsCollection: barsController.chartsCollection, isZoomed: false, animated: false) + } + + func switchToChart(chartsCollection: ChartsCollection, isZoomed: Bool, animated: Bool) { + if animated { + TimeInterval.setDefaultSuration(.expandAnimationDuration) + DispatchQueue.main.asyncAfter(deadline: .now() + .expandAnimationDuration) { + TimeInterval.setDefaultSuration(.osXDuration) + } + } + + super.isZoomed = isZoomed + if isZoomed { + let toHorizontalRange = zoomedBarsController.initialHorizontalRange + let destinationHorizontalRange = (toHorizontalRange.lowerBound - barsController.barsWidth)...(toHorizontalRange.upperBound - barsController.barsWidth) + let verticalVisibleRange = barsController.currentVerticalMainChartRange + let initialVerticalRange = verticalVisibleRange.lowerBound...(verticalVisibleRange.upperBound + verticalVisibleRange.distance * 10) + + zoomedBarsController.mainBarsRenderer.setup(horizontalRange: barsController.currentHorizontalMainChartRange, animated: false) + zoomedBarsController.previewBarsChartRenderer.setup(horizontalRange: barsController.currentPreviewHorizontalRange, animated: false) + zoomedBarsController.mainBarsRenderer.setup(verticalRange: initialVerticalRange, animated: false) + zoomedBarsController.previewBarsChartRenderer.setup(verticalRange: initialVerticalRange, animated: false) + zoomedBarsController.mainBarsRenderer.setVisible(true, animated: false) + zoomedBarsController.previewBarsChartRenderer.setVisible(true, animated: false) + + barsController.setupMainChart(horizontalRange: destinationHorizontalRange, animated: animated) + barsController.previewBarsChartRenderer.setup(horizontalRange: zoomedBarsController.totalHorizontalRange, animated: animated) + barsController.mainBarsRenderer.setVisible(false, animated: animated) + barsController.previewBarsChartRenderer.setVisible(false, animated: animated) + + zoomedBarsController.willAppear(animated: animated) + barsController.willDisappear(animated: animated) + + zoomedBarsController.updateChartsVisibility(visibility: barsController.chartVisibility, animated: false) + zoomedBarsController.mainBarsRenderer.setup(verticalRange: zoomedBarsController.currentVerticalMainChartRange, animated: animated, timeFunction: .easeOut) + zoomedBarsController.previewBarsChartRenderer.setup(verticalRange: zoomedBarsController.currentPreviewVerticalRange, animated: animated, timeFunction: .easeOut) + } else { + if !zoomedBarsController.chartsCollection.isBlank { + barsController.hideDetailsView(animated: false) + barsController.chartVisibility = zoomedBarsController.chartVisibility + let visibleVerticalRange = BarChartRenderer.BarsData.verticalRange(bars: barsController.visibleBars, + calculatingRange: barsController.initialHorizontalRange) ?? BaseConstants.defaultRange + barsController.mainBarsRenderer.setup(verticalRange: visibleVerticalRange, animated: false) + + let toHorizontalRange = barsController.initialHorizontalRange + + let verticalVisibleRange = barsController.initialVerticalRange + let targetVerticalRange = verticalVisibleRange.lowerBound...(verticalVisibleRange.upperBound + verticalVisibleRange.distance * 10) + + zoomedBarsController.setupMainChart(horizontalRange: toHorizontalRange, animated: animated) + zoomedBarsController.mainBarsRenderer.setup(verticalRange: targetVerticalRange, animated: animated, timeFunction: .easeIn) + zoomedBarsController.previewBarsChartRenderer.setup(verticalRange: targetVerticalRange, animated: animated, timeFunction: .easeIn) + zoomedBarsController.previewBarsChartRenderer.setup(horizontalRange: barsController.totalHorizontalRange, animated: animated) + DispatchQueue.main.asyncAfter(deadline: .now() + .defaultDuration) { [weak self] in + self?.zoomedBarsController.mainBarsRenderer.setVisible(false, animated: false) + self?.zoomedBarsController.previewBarsChartRenderer.setVisible(false, animated: false) + } + } + + barsController.willAppear(animated: animated) + zoomedBarsController.willDisappear(animated: animated) + + if !zoomedBarsController.chartsCollection.isBlank { + barsController.updateChartsVisibility(visibility: zoomedBarsController.chartVisibility, animated: false) + } + } + + self.setBackButtonVisibilityClosure?(isZoomed, animated) + } + + public override func updateChartsVisibility(visibility: [Bool], animated: Bool) { + if isZoomed { + zoomedBarsController.updateChartsVisibility(visibility: visibility, animated: animated) + } else { + barsController.updateChartsVisibility(visibility: visibility, animated: animated) + } + } + + var visibleChartValues: [ChartsCollection.Chart] { + let visibility = isZoomed ? zoomedBarsController.chartVisibility : barsController.chartVisibility + let collection = isZoomed ? zoomedBarsController.chartsCollection : barsController.chartsCollection + let visibleCharts: [ChartsCollection.Chart] = visibility.enumerated().compactMap { args in + args.element ? collection.chartValues[args.offset] : nil + } + return visibleCharts + } + + public override var actualChartVisibility: [Bool] { + return isZoomed ? zoomedBarsController.chartVisibility : barsController.chartVisibility + } + + public override var actualChartsCollection: ChartsCollection { + let collection = isZoomed ? zoomedBarsController.chartsCollection : barsController.chartsCollection + if collection.isBlank { + return self.initialChartsCollection + } + return collection + } + + public override func chartInteractionDidBegin(point: CGPoint) { + if isZoomed { + zoomedBarsController.chartInteractionDidBegin(point: point) + } else { + barsController.chartInteractionDidBegin(point: point) + } + } + + public override func chartInteractionDidEnd() { + if isZoomed { + zoomedBarsController.chartInteractionDidEnd() + } else { + barsController.chartInteractionDidEnd() + } + } + + public override var drawChartVisibity: Bool { + return true + } + + public override var currentChartHorizontalRangeFraction: ClosedRange { + if isZoomed { + return zoomedBarsController.currentChartHorizontalRangeFraction + } else { + return barsController.currentChartHorizontalRangeFraction + } + } + + public override func cancelChartInteraction() { + if isZoomed { + return zoomedBarsController.hideDetailsView(animated: true) + } else { + return barsController.hideDetailsView(animated: true) + } + } + + public override func didTapZoomIn(date: Date) { + guard isZoomed == false else { return } + if isZoomed { + return zoomedBarsController.hideDetailsView(animated: true) + } + self.getDetailsData?(date, { updatedCollection in + if let updatedCollection = updatedCollection { + self.zoomedBarsController.initialize(chartsCollection: updatedCollection, + initialDate: date, + totalHorizontalRange: 0...1, + totalVerticalRange: 0...1) + self.switchToChart(chartsCollection: updatedCollection, isZoomed: true, animated: true) + } + }) + } + + public override func didTapZoomOut() { + cancelChartInteraction() + switchToChart(chartsCollection: barsController.chartsCollection, isZoomed: false, animated: true) + } + + public override func updateChartRange(_ rangeFraction: ClosedRange) { + if isZoomed { + return zoomedBarsController.chartRangeFractionDidUpdated(rangeFraction) + } else { + return barsController.chartRangeFractionDidUpdated(rangeFraction) + } + } + + public override func apply(colorMode: GColorMode, animated: Bool) { + super.apply(colorMode: colorMode, animated: animated) + + zoomedBarsController.apply(colorMode: colorMode, animated: animated) + barsController.apply(colorMode: colorMode, animated: animated) + } +} diff --git a/submodules/GraphCore/src/Charts/Renderes/BarChartRenderer.swift b/submodules/GraphCore/src/Charts/Renderes/BarChartRenderer.swift new file mode 100644 index 0000000000..7be512d223 --- /dev/null +++ b/submodules/GraphCore/src/Charts/Renderes/BarChartRenderer.swift @@ -0,0 +1,298 @@ +// +// BarChartRenderer.swift +// GraphTest +// +// Created by Andrei Salavei on 4/7/19. +// Copyright © 2019 Andrei Salavei. All rights reserved. +// + +import Foundation +#if os(macOS) +import Cocoa +#else +import UIKit +#endif + +class BarChartRenderer: BaseChartRenderer { + struct BarsData { + static let blank = BarsData(barWidth: 1, locations: [], components: []) + var barWidth: CGFloat + var locations: [CGFloat] + var components: [Component] + + struct Component { + var color: GColor + var values: [CGFloat] + } + } + + var fillToTop: Bool = false + private(set) lazy var selectedIndexAnimator: AnimationController = { + return AnimationController(current: 0, refreshClosure: self.refreshClosure) + }() + func setSelectedIndex(_ index: Int?, animated: Bool) { + let destinationValue: CGFloat = (index == nil) ? 0 : 1 + if animated { + if index != nil { + selectedBarIndex = index + } + self.selectedIndexAnimator.completionClosure = { + self.selectedBarIndex = index + } + guard self.selectedIndexAnimator.end != destinationValue else { return } + self.selectedIndexAnimator.animate(to: destinationValue, duration: .defaultDuration) + } else { + self.selectedIndexAnimator.set(current: destinationValue) + self.selectedBarIndex = index + } + } + + private var selectedBarIndex: Int? { + didSet { + setNeedsDisplay() + } + } + var generalUnselectedAlpha: CGFloat = 0.5 + + private var componentsAnimators: [AnimationController] = [] + var bars: BarsData = BarsData(barWidth: 1, locations: [], components: []) { + willSet { + if bars.components.count != newValue.components.count { + componentsAnimators = newValue.components.map { _ in AnimationController(current: 1, refreshClosure: self.refreshClosure) } + } + } + didSet { + setNeedsDisplay() + } + } + + func setComponentVisible(_ isVisible: Bool, at index: Int, animated: Bool) { + componentsAnimators[index].animate(to: isVisible ? 1 : 0, duration: animated ? .defaultDuration : 0) + } + + private lazy var backgroundColorAnimator = AnimationController(current: NSColorContainer(color: .white), refreshClosure: refreshClosure) + func update(backgroundColor: GColor, animated: Bool) { + if animated { + backgroundColorAnimator.animate(to: NSColorContainer(color: backgroundColor), duration: .defaultDuration) + } else { + backgroundColorAnimator.set(current: NSColorContainer(color: backgroundColor)) + } + } + + override func render(context: CGContext, bounds: CGRect, chartFrame: CGRect) { + guard isEnabled && verticalRange.current.distance > 0 && verticalRange.current.distance > 0 else { return } + let chartsAlpha = chartAlphaAnimator.current + if chartsAlpha == 0 { return } + + let range = renderRange(bounds: bounds, chartFrame: chartFrame) + + var selectedPaths: [[CGRect]] = bars.components.map { _ in [] } + var unselectedPaths: [[CGRect]] = bars.components.map { _ in [] } + + if var barIndex = bars.locations.firstIndex(where: { $0 >= range.lowerBound }) { + if fillToTop { + barIndex = max(0, barIndex - 1) + + while barIndex < bars.locations.count { + let currentLocation = bars.locations[barIndex] + let right = transform(toChartCoordinateHorizontal: currentLocation, chartFrame: chartFrame).roundedUpToPixelGrid() + let left = transform(toChartCoordinateHorizontal: currentLocation - bars.barWidth, chartFrame: chartFrame).roundedUpToPixelGrid() + + var summ: CGFloat = 0 + for (index, component) in bars.components.enumerated() { + summ += componentsAnimators[index].current * component.values[barIndex] + } + guard summ > 0 else { + barIndex += 1 + continue + } + + var stackedValue: CGFloat = 0 + for (index, component) in bars.components.enumerated() { + let visibilityPercent = componentsAnimators[index].current + if visibilityPercent == 0 { continue } + + let bottomFraction = stackedValue + let topFraction = stackedValue + ((component.values[barIndex] * visibilityPercent) / summ) + + let rect = CGRect(x: left, + y: chartFrame.maxY - chartFrame.height * topFraction, + width: right - left, + height: chartFrame.height * (topFraction - bottomFraction)) + if selectedBarIndex == barIndex { + selectedPaths[index].append(rect) + } else { + unselectedPaths[index].append(rect) + } + stackedValue = topFraction + } + if currentLocation > range.upperBound { + break + } + barIndex += 1 + } + + for (index, component) in bars.components.enumerated() { + context.saveGState() + context.setFillColor(component.color.withAlphaComponent(chartsAlpha * component.color.alphaValue).cgColor) + context.fill(selectedPaths[index]) + let resultAlpha: CGFloat = 1.0 - (1.0 - generalUnselectedAlpha) * selectedIndexAnimator.current + context.setFillColor(component.color.withAlphaComponent(chartsAlpha * component.color.alphaValue * resultAlpha).cgColor) + context.fill(unselectedPaths[index]) + context.restoreGState() + } + } else { + var selectedPaths: [[CGRect]] = bars.components.map { _ in [] } + barIndex = max(0, barIndex - 1) + + var currentLocation = bars.locations[barIndex] + var leftX = transform(toChartCoordinateHorizontal: currentLocation - bars.barWidth, chartFrame: chartFrame) + var rightX: CGFloat = 0 + + let startPoint = CGPoint(x: leftX, + y: transform(toChartCoordinateVertical: verticalRange.current.lowerBound, chartFrame: chartFrame)) + + var backgourndPaths: [[CGPoint]] = bars.components.map { _ in Array() } + let itemsCount = ((bars.locations.count - barIndex) * 2) + 4 + for path in backgourndPaths.indices { + backgourndPaths[path].reserveCapacity(itemsCount) + backgourndPaths[path].append(startPoint) + } + var maxValues: [CGFloat] = bars.components.map { _ in 0 } + while barIndex < bars.locations.count { + currentLocation = bars.locations[barIndex] + rightX = transform(toChartCoordinateHorizontal: currentLocation, chartFrame: chartFrame) + + var stackedValue: CGFloat = 0 + var bottomY: CGFloat = transform(toChartCoordinateVertical: stackedValue, chartFrame: chartFrame) + for (index, component) in bars.components.enumerated() { + let visibilityPercent = componentsAnimators[index].current + if visibilityPercent == 0 { continue } + + let height = component.values[barIndex] * visibilityPercent + stackedValue += height + let topY = transform(toChartCoordinateVertical: stackedValue, chartFrame: chartFrame) + let componentHeight = (bottomY - topY) + maxValues[index] = max(maxValues[index], componentHeight) + if selectedBarIndex == barIndex { + let rect = CGRect(x: leftX, + y: topY, + width: rightX - leftX, + height: componentHeight) + selectedPaths[index].append(rect) + } + backgourndPaths[index].append(CGPoint(x: leftX, y: topY)) + backgourndPaths[index].append(CGPoint(x: rightX, y: topY)) + bottomY = topY + } + if currentLocation > range.upperBound { + break + } + leftX = rightX + barIndex += 1 + } + + let endPoint = CGPoint(x: transform(toChartCoordinateHorizontal: currentLocation, chartFrame: chartFrame).roundedUpToPixelGrid(), + y: transform(toChartCoordinateVertical: verticalRange.current.lowerBound, chartFrame: chartFrame)) + let colorOffset = Double((1.0 - (1.0 - generalUnselectedAlpha) * selectedIndexAnimator.current) * chartsAlpha) + + for (index, component) in bars.components.enumerated().reversed() { + if maxValues[index] < optimizationLevel { + continue + } + context.saveGState() + backgourndPaths[index].append(endPoint) + + context.setFillColor(GColor.valueBetween(start: backgroundColorAnimator.current.color, + end: component.color, + offset: colorOffset).cgColor) + context.beginPath() + context.addLines(between: backgourndPaths[index]) + context.closePath() + context.fillPath() + context.restoreGState() + } + + for (index, component) in bars.components.enumerated().reversed() { + context.setFillColor(component.color.withAlphaComponent(chartsAlpha * component.color.alphaValue).cgColor) + context.fill(selectedPaths[index]) + } + } + } + } +} + +extension BarChartRenderer.BarsData { + static func initialComponents(chartsCollection: ChartsCollection) -> + (width: CGFloat, + chartBars: BarChartRenderer.BarsData, + totalHorizontalRange: ClosedRange, + totalVerticalRange: ClosedRange) { + let width: CGFloat + if chartsCollection.axisValues.count > 1 { + width = CGFloat(abs(chartsCollection.axisValues[1].timeIntervalSince1970 - chartsCollection.axisValues[0].timeIntervalSince1970)) + } else { + width = 1 + } + let components = chartsCollection.chartValues.map { BarChartRenderer.BarsData.Component(color: $0.color, + values: $0.values.map { CGFloat($0) }) } + let chartBars = BarChartRenderer.BarsData(barWidth: width, + locations: chartsCollection.axisValues.map { CGFloat($0.timeIntervalSince1970) }, + components: components) + + + + let totalVerticalRange = BarChartRenderer.BarsData.verticalRange(bars: chartBars) ?? 0...1 + let totalHorizontalRange = BarChartRenderer.BarsData.visibleHorizontalRange(bars: chartBars, width: width) ?? 0...1 + return (width: width, chartBars: chartBars, totalHorizontalRange: totalHorizontalRange, totalVerticalRange: totalVerticalRange) + } + + static func visibleHorizontalRange(bars: BarChartRenderer.BarsData, width: CGFloat) -> ClosedRange? { + guard let firstPoint = bars.locations.first, + let lastPoint = bars.locations.last, + firstPoint <= lastPoint else { + return nil + } + + return (firstPoint - width)...lastPoint + } + + static func verticalRange(bars: BarChartRenderer.BarsData, calculatingRange: ClosedRange? = nil, addBounds: Bool = false) -> ClosedRange? { + guard bars.components.count > 0 else { + return nil + } + if let calculatingRange = calculatingRange { + guard var index = bars.locations.firstIndex(where: { $0 >= calculatingRange.lowerBound && $0 <= calculatingRange.upperBound }) else { + return nil + } + + var vMax: CGFloat = bars.components[0].values[index] + while index < bars.locations.count { + var summ: CGFloat = 0 + for component in bars.components { + summ += component.values[index] + } + vMax = max(vMax, summ) + + if bars.locations[index] > calculatingRange.upperBound { + break + } + index += 1 + } + return 0...vMax + } else { + var index = 0 + + var vMax: CGFloat = bars.components[0].values[index] + while index < bars.locations.count { + var summ: CGFloat = 0 + for component in bars.components { + summ += component.values[index] + } + vMax = max(vMax, summ) + index += 1 + } + return 0...vMax + } + } +} diff --git a/submodules/GraphCore/src/Charts/Renderes/BaseChartRenderer.swift b/submodules/GraphCore/src/Charts/Renderes/BaseChartRenderer.swift new file mode 100644 index 0000000000..fd521baa58 --- /dev/null +++ b/submodules/GraphCore/src/Charts/Renderes/BaseChartRenderer.swift @@ -0,0 +1,127 @@ +// +// BaseChartRenderer.swift +// GraphTest +// +// Created by Andrei Salavei on 4/7/19. +// Copyright © 2019 Andrei Salavei. All rights reserved. +// + +import Foundation +#if os(macOS) +import Cocoa +#else +import UIKit +#endif + +public protocol ChartViewRenderer: class { + var containerViews: [GView] { get set } + func render(context: CGContext, bounds: CGRect, chartFrame: CGRect) +} + + +private let exponentialAnimationTrashold: CGFloat = 100 + +class BaseChartRenderer: ChartViewRenderer { + var containerViews: [GView] = [] + + var optimizationLevel: CGFloat = 1 { + didSet { + setNeedsDisplay() + } + } + var isEnabled: Bool = true { + didSet { + setNeedsDisplay() + } + } + + private(set) lazy var chartAlphaAnimator: AnimationController = { + return AnimationController(current: 1, refreshClosure: self.refreshClosure) + }() + func setVisible(_ visible: Bool, animated: Bool) { + let destinationValue: CGFloat = visible ? 1 : 0 + guard self.chartAlphaAnimator.end != destinationValue else { return } + if animated { + self.chartAlphaAnimator.animate(to: destinationValue, duration: .defaultDuration) + } else { + self.chartAlphaAnimator.set(current: destinationValue) + } + } + + lazy var horizontalRange = AnimationController>(current: 0...1, refreshClosure: refreshClosure) + lazy var verticalRange = AnimationController>(current: 0...1, refreshClosure: refreshClosure) + + func setup(verticalRange: ClosedRange, animated: Bool, timeFunction: TimeFunction? = nil) { + guard self.verticalRange.end != verticalRange else { + self.verticalRange.timeFunction = timeFunction ?? .linear + return + } + if animated { + let function: TimeFunction + if let timeFunction = timeFunction { + function = timeFunction + } else if self.verticalRange.current.distance > 0 && verticalRange.distance > 0 { + if self.verticalRange.current.distance / verticalRange.distance > exponentialAnimationTrashold { + function = .easeIn + } else if verticalRange.distance / self.verticalRange.current.distance > exponentialAnimationTrashold { + function = .easeOut + } else { + function = .linear + } + } else { + function = .linear + } + + self.verticalRange.animate(to: verticalRange, duration: .defaultDuration, timeFunction: function) + } else { + self.verticalRange.set(current: verticalRange) + } + } + + func setup(horizontalRange: ClosedRange, animated: Bool) { + guard self.horizontalRange.end != horizontalRange else { return } + if animated { + let animationCurve: TimeFunction = self.horizontalRange.current.distance > horizontalRange.distance ? .easeOut : .easeIn + self.horizontalRange.animate(to: horizontalRange, duration: .defaultDuration, timeFunction: animationCurve) + } else { + self.horizontalRange.set(current: horizontalRange) + } + } + + func transform(toChartCoordinateHorizontal x: CGFloat, chartFrame: CGRect) -> CGFloat { + return chartFrame.origin.x + (x - horizontalRange.current.lowerBound) / horizontalRange.current.distance * chartFrame.width + } + + func transform(toChartCoordinateVertical y: CGFloat, chartFrame: CGRect) -> CGFloat { + return chartFrame.height + chartFrame.origin.y - (y - verticalRange.current.lowerBound) / verticalRange.current.distance * chartFrame.height + } + + func transform(toChartCoordinate point: CGPoint, chartFrame: CGRect) -> CGPoint { + return CGPoint(x: transform(toChartCoordinateHorizontal: point.x, chartFrame: chartFrame), + y: transform(toChartCoordinateVertical: point.y, chartFrame: chartFrame)) + } + + func renderRange(bounds: CGRect, chartFrame: CGRect) -> ClosedRange { + let lowerBound = horizontalRange.current.lowerBound - chartFrame.origin.x / chartFrame.width * horizontalRange.current.distance + let upperBound = horizontalRange.current.upperBound + (bounds.width - chartFrame.width - chartFrame.origin.x) / chartFrame.width * horizontalRange.current.distance + guard lowerBound <= upperBound else { + print("Error: Unexpecated bounds range!") + return 0...1 + } + return lowerBound...upperBound + } + + func render(context: CGContext, bounds: CGRect, chartFrame: CGRect) { + fatalError("abstract") + } + + func setNeedsDisplay() { + containerViews.forEach { $0.setNeedsDisplay($0.bounds) } + } + + var refreshClosure: () -> Void { + return { [weak self] in + self?.setNeedsDisplay() + } + } +} diff --git a/submodules/GraphCore/src/Charts/Renderes/ChartDetailsRenderer.swift b/submodules/GraphCore/src/Charts/Renderes/ChartDetailsRenderer.swift new file mode 100644 index 0000000000..f1860f52c3 --- /dev/null +++ b/submodules/GraphCore/src/Charts/Renderes/ChartDetailsRenderer.swift @@ -0,0 +1,160 @@ +// +// ChartDetailsRenderer.swift +// GraphTest +// +// Created by Andrei Salavei on 4/13/19. +// Copyright © 2019 Andrei Salavei. All rights reserved. +// + +import Foundation +#if os(macOS) +import Cocoa +#else +import UIKit +#endif + +class ChartDetailsRenderer: BaseChartRenderer, GColorModeContainer { + private lazy var colorAnimator = AnimationController(current: 1, refreshClosure: refreshClosure) + private var fromColorMode: GColorMode = .day + private var currentColorMode: GColorMode = .day + func apply(colorMode: GColorMode, animated: Bool) { + if currentColorMode != colorMode { + fromColorMode = currentColorMode + currentColorMode = colorMode + if animated { + colorAnimator.set(current: 0) + colorAnimator.animate(to: 1, duration: .defaultDuration) + } else { + colorAnimator.set(current: 1) + } + } + } + + private var valuesAnimators: [AnimationController] = [] + func setValueVisible(_ isVisible: Bool, at index: Int, animated: Bool) { + valuesAnimators[index].animate(to: isVisible ? 1 : 0, duration: animated ? .defaultDuration : 0) + } + var detailsViewModel: ChartDetailsViewModel = .blank { + didSet { + if detailsViewModel.values.count != valuesAnimators.count { + valuesAnimators = detailsViewModel.values.map { _ in AnimationController(current: 1, refreshClosure: refreshClosure) } + } + setNeedsDisplay() + } + } + + var detailsViewPosition: CGFloat = 0 { + didSet { + setNeedsDisplay() + } + } + var detailViewPositionOffset: CGFloat = 10 + var detailViewTopOffset: CGFloat = 10 + private var iconWidth: CGFloat = 10 + private var margins: CGFloat = 10 + private let cornerRadius: CGFloat = 5 + private var rowHeight: CGFloat = 20 + private let titleFont = NSFont.systemFont(ofSize: 14, weight: .bold) + private let prefixFont = NSFont.systemFont(ofSize: 14, weight: .bold) + private let labelsFont = NSFont.systemFont(ofSize: 14, weight: .medium) + private let valuesFont = NSFont.systemFont(ofSize: 14, weight: .bold) + private let labelsColor: GColor = .black + + private(set) var previousRenderBannerFrame: CGRect = .zero + override func render(context: CGContext, bounds: CGRect, chartFrame: CGRect) { + previousRenderBannerFrame = .zero + guard isEnabled && verticalRange.current.distance > 0 && verticalRange.current.distance > 0 else { return } + let generalAlpha = chartAlphaAnimator.current + if generalAlpha == 0 { return } + + let widths: [(prefix: CGFloat, label: CGFloat, value: CGFloat)] = detailsViewModel.values.map { value in + var prefixWidth: CGFloat = 0 + if let prefixText = value.prefix { + prefixWidth = (prefixText as NSString).boundingRect(with: bounds.size, + options: .usesLineFragmentOrigin, + attributes: [.font: prefixFont], + context: nil).width.rounded(.up) + margins + } + + let labelWidth = (value.title as NSString).boundingRect(with: bounds.size, + options: .usesLineFragmentOrigin, + attributes: [.font: labelsFont], + context: nil).width.rounded(.up) + margins + + let valueWidth = (value.value as NSString).boundingRect(with: bounds.size, + options: .usesLineFragmentOrigin, + attributes: [.font: valuesFont], + context: nil).width.rounded(.up) + return (prefixWidth, labelWidth, valueWidth) + } + + let titleWidth = (detailsViewModel.title as NSString).boundingRect(with: bounds.size, + options: .usesLineFragmentOrigin, + attributes: [.font: titleFont], + context: nil).width + let prefixesWidth = widths.map { $0.prefix }.max() ?? 0 + let labelsWidth = widths.map { $0.label }.max() ?? 0 + let valuesWidth = widths.map { $0.value }.max() ?? 0 + + let totalWidth: CGFloat = max(prefixesWidth + labelsWidth + valuesWidth, titleWidth + iconWidth) + margins * 2 + let totalHeight: CGFloat = CGFloat(detailsViewModel.values.count + 1) * rowHeight + margins * 2 + let backgroundColor = GColor.valueBetween(start: fromColorMode.chartDetailsViewColor, + end: currentColorMode.chartDetailsViewColor, + offset: Double(colorAnimator.current)) + let titleAndTextColor = GColor.valueBetween(start: fromColorMode.chartDetailsTextColor, + end: currentColorMode.chartDetailsTextColor, + offset: Double(colorAnimator.current)) + let detailsViewFrame: CGRect + if totalWidth + detailViewTopOffset > detailsViewPosition { + detailsViewFrame = CGRect(x: detailsViewPosition + detailViewTopOffset, + y: detailViewTopOffset + chartFrame.minY, + width: totalWidth, + height: totalHeight) + } else { + detailsViewFrame = CGRect(x: detailsViewPosition - totalWidth - detailViewTopOffset, + y: detailViewTopOffset + chartFrame.minY, + width: totalWidth, + height: totalHeight) + } + previousRenderBannerFrame = detailsViewFrame + context.saveGState() + context.setFillColor(backgroundColor.cgColor) + context.beginPath() + context.addPath(CGPath(roundedRect: detailsViewFrame, cornerWidth: 5, cornerHeight: 5, transform: nil)) + context.fillPath() + context.endPage() + context.restoreGState() + + var drawY = detailsViewFrame.minY + margins + (rowHeight - titleFont.pointSize) / 2 + + + + let attributedString = NSAttributedString(string: detailsViewModel.title, attributes: [.foregroundColor: titleAndTextColor, .font: titleFont]) + let textNode = LabelNode.layoutText(attributedString, bounds.size) + textNode.1.draw(CGRect(origin: CGPoint(x: detailsViewFrame.minX + margins, y: drawY), size: textNode.0.size), in: context, backingScaleFactor: deviceScale) + + drawY += rowHeight + + for (index, row) in widths.enumerated() { + let value = detailsViewModel.values[index] + if let prefixText = value.prefix { + + let attributedString = NSAttributedString(string: prefixText, attributes: [.foregroundColor: titleAndTextColor, .font: prefixFont]) + let textNode = LabelNode.layoutText(attributedString, bounds.size) + textNode.1.draw(CGRect(origin: CGPoint(x: detailsViewFrame.minX + prefixesWidth - row.prefix, + y: drawY), size: textNode.0.size), in: context, backingScaleFactor: deviceScale) + } + + var attributedString = NSAttributedString(string: value.title, attributes: [.foregroundColor: titleAndTextColor, .font: labelsFont]) + var textNode = LabelNode.layoutText(attributedString, bounds.size) + textNode.1.draw(CGRect(origin: CGPoint(x: detailsViewFrame.minX + prefixesWidth + margins, + y: drawY), size: textNode.0.size), in: context, backingScaleFactor: deviceScale) + + attributedString = NSAttributedString(string: value.title, attributes: [.foregroundColor: value.color, .font: labelsFont]) + textNode = LabelNode.layoutText(attributedString, bounds.size) + textNode.1.draw(CGRect(origin: CGPoint(x: detailsViewFrame.minX + prefixesWidth + labelsWidth + valuesWidth - row.value + margins, y: drawY), size: textNode.0.size), in: context, backingScaleFactor: deviceScale) + + drawY += rowHeight + } + } +} diff --git a/submodules/GraphCore/src/Charts/Renderes/HorizontalScalesRenderer.swift b/submodules/GraphCore/src/Charts/Renderes/HorizontalScalesRenderer.swift new file mode 100644 index 0000000000..db5e2524e7 --- /dev/null +++ b/submodules/GraphCore/src/Charts/Renderes/HorizontalScalesRenderer.swift @@ -0,0 +1,102 @@ +// +// HorizontalScalesRenderer.swift +// GraphTest +// +// Created by Andrei Salavei on 4/8/19. +// Copyright © 2019 Andrei Salavei. All rights reserved. +// + +import Foundation +#if os(macOS) +import Cocoa +#else +import UIKit +#endif + +class HorizontalScalesRenderer: BaseChartRenderer { + private var horizontalLabels: [LinesChartLabel] = [] + private var animatedHorizontalLabels: [AnimatedLinesChartLabels] = [] + + var labelsVerticalOffset: CGFloat = 8 + var labelsFont: NSFont = .systemFont(ofSize: 11) + var labelsColor: GColor = .gray + + func setup(labels: [LinesChartLabel], animated: Bool) { + if animated { + var labelsToKeepVisible: [LinesChartLabel] = [] + let labelsToHide: [LinesChartLabel] + var labelsToShow: [LinesChartLabel] = [] + + for label in labels { + if horizontalLabels.contains(label) { + labelsToKeepVisible.append(label) + } else { + labelsToShow.append(label) + } + } + labelsToHide = horizontalLabels.filter { !labels.contains($0) } + animatedHorizontalLabels.removeAll() + horizontalLabels = labelsToKeepVisible + + let showAnimation = AnimatedLinesChartLabels(labels: labelsToShow, alphaAnimator: AnimationController(current: 1.0, refreshClosure: refreshClosure)) + showAnimation.isAppearing = true + showAnimation.alphaAnimator.set(current: 0) + showAnimation.alphaAnimator.animate(to: 1, duration: .defaultDuration) + showAnimation.alphaAnimator.completionClosure = { [weak self, weak showAnimation] in + guard let self = self, let showAnimation = showAnimation else { return } + self.animatedHorizontalLabels.removeAll(where: { $0 === showAnimation }) + self.horizontalLabels = labels + } + + let hideAnimation = AnimatedLinesChartLabels(labels: labelsToHide, alphaAnimator: AnimationController(current: 1.0, refreshClosure: refreshClosure)) + hideAnimation.isAppearing = false + hideAnimation.alphaAnimator.set(current: 1) + hideAnimation.alphaAnimator.animate(to: 0, duration: .defaultDuration) + hideAnimation.alphaAnimator.completionClosure = { [weak self, weak hideAnimation] in + guard let self = self, let hideAnimation = hideAnimation else { return } + self.animatedHorizontalLabels.removeAll(where: { $0 === hideAnimation }) + } + + animatedHorizontalLabels.append(showAnimation) + animatedHorizontalLabels.append(hideAnimation) + } else { + horizontalLabels = labels + animatedHorizontalLabels = [] + } + } + + override func render(context: CGContext, bounds: CGRect, chartFrame: CGRect) { + guard isEnabled && verticalRange.current.distance > 0 && verticalRange.current.distance > 0 else { return } + let itemsAlpha = chartAlphaAnimator.current + guard itemsAlpha > 0 else { return } + + let range = renderRange(bounds: bounds, chartFrame: chartFrame) + + func drawHorizontalLabels(_ labels: [LinesChartLabel], color: GColor) { + let y = chartFrame.origin.y + chartFrame.height + labelsVerticalOffset + + if let start = labels.firstIndex(where: { $0.value > range.lowerBound }) { + for index in start.. range.upperBound { + break + } + } + } + } + let labelColorAlpha = labelsColor.alphaValue * itemsAlpha + drawHorizontalLabels(horizontalLabels, color: labelsColor.withAlphaComponent(labelColorAlpha * itemsAlpha)) + for animation in animatedHorizontalLabels { + let color = labelsColor.withAlphaComponent(animation.alphaAnimator.current * labelColorAlpha) + drawHorizontalLabels(animation.labels, color: color) + } + } +} diff --git a/submodules/GraphCore/src/Charts/Renderes/LineBulletsRenerer.swift b/submodules/GraphCore/src/Charts/Renderes/LineBulletsRenerer.swift new file mode 100644 index 0000000000..13c7543286 --- /dev/null +++ b/submodules/GraphCore/src/Charts/Renderes/LineBulletsRenerer.swift @@ -0,0 +1,72 @@ +// +// LineBulletsRenerer.swift +// GraphTest +// +// Created by Andrei Salavei on 4/8/19. +// Copyright © 2019 Andrei Salavei. All rights reserved. +// + +import Foundation +#if os(macOS) +import Cocoa +#else +import UIKit +#endif + +class LineBulletsRenerer: BaseChartRenderer { + struct Bullet { + var coordinate: CGPoint + var color: GColor + } + + var bullets: [Bullet] = [] { + willSet { + if alphaAnimators.count != newValue.count { + alphaAnimators = newValue.map { _ in AnimationController(current: 1.0, refreshClosure: refreshClosure) } + } + } + didSet { + setNeedsDisplay() + } + } + private var alphaAnimators: [AnimationController] = [] + + private lazy var innerColorAnimator = AnimationController(current: NSColorContainer(color: .white), refreshClosure: refreshClosure) + public func setInnerColor(_ color: GColor, animated: Bool) { + if animated { + innerColorAnimator.animate(to: NSColorContainer(color: color), duration: .defaultDuration) + } else { + innerColorAnimator.set(current: NSColorContainer(color: color)) + } + } + + var linesWidth: CGFloat = 2 + var bulletRadius: CGFloat = 6 + + func setLineVisible(_ isVisible: Bool, at index: Int, animated: Bool) { + alphaAnimators[index].animate(to: isVisible ? 1 : 0, duration: animated ? .defaultDuration : 0) + } + + override func render(context: CGContext, bounds: CGRect, chartFrame: CGRect) { + guard isEnabled && verticalRange.current.distance > 0 && verticalRange.current.distance > 0 else { return } + let generalAlpha = chartAlphaAnimator.current + if generalAlpha == 0 { return } + + for (index, bullet) in bullets.enumerated() { + let alpha = alphaAnimators[index].current + if alpha == 0 { continue } + + let centerX = transform(toChartCoordinateHorizontal: bullet.coordinate.x, chartFrame: chartFrame) + let centerY = transform(toChartCoordinateVertical: bullet.coordinate.y, chartFrame: chartFrame) + context.setFillColor(innerColorAnimator.current.color.withAlphaComponent(alpha).cgColor) + context.setStrokeColor(bullet.color.withAlphaComponent(alpha).cgColor) + context.setLineWidth(linesWidth) + let rect = CGRect(x: centerX - bulletRadius / 2, + y: centerY - bulletRadius / 2, + width: bulletRadius, + height: bulletRadius) + context.fillEllipse(in: rect) + context.strokeEllipse(in: rect) + } + } +} diff --git a/submodules/GraphCore/src/Charts/Renderes/LinesChartRenderer.swift b/submodules/GraphCore/src/Charts/Renderes/LinesChartRenderer.swift new file mode 100644 index 0000000000..a760ab0b11 --- /dev/null +++ b/submodules/GraphCore/src/Charts/Renderes/LinesChartRenderer.swift @@ -0,0 +1,543 @@ +// +// LinesChartRenderer.swift +// GraphTest +// +// Created by Andrei Salavei on 4/7/19. +// Copyright © 2019 Andrei Salavei. All rights reserved. +// + +import Foundation +#if os(macOS) +import Cocoa +#else +import UIKit +#endif + +class LinesChartRenderer: BaseChartRenderer { + struct LineData { + var color: GColor + var points: [CGPoint] + } + + private var linesAlphaAnimators: [AnimationController] = [] + + var lineWidth: CGFloat = 1 { + didSet { + setNeedsDisplay() + } + } + private lazy var linesShapeAnimator = AnimationController(current: 1, refreshClosure: self.refreshClosure) + private var fromLines: [LineData] = [] + private var toLines: [LineData] = [] + + func setLines(lines: [LineData], animated: Bool) { + if toLines.count != lines.count { + linesAlphaAnimators = lines.map { _ in AnimationController(current: 1, refreshClosure: self.refreshClosure) } + } + if animated { + self.fromLines = self.toLines + self.toLines = lines + linesShapeAnimator.set(current: 1.0 - linesShapeAnimator.current) + linesShapeAnimator.completionClosure = { + self.fromLines = [] + } + linesShapeAnimator.animate(to: 1, duration: .defaultDuration) + } else { + self.fromLines = [] + self.toLines = lines + linesShapeAnimator.set(current: 1) + } + } + + func setLineVisible(_ isVisible: Bool, at index: Int, animated: Bool) { + linesAlphaAnimators[index].animate(to: isVisible ? 1 : 0, duration: animated ? .defaultDuration : 0) + } + + override func render(context: CGContext, bounds: CGRect, chartFrame: CGRect) { + guard isEnabled && verticalRange.current.distance > 0 && verticalRange.current.distance > 0 else { return } + let chartsAlpha = chartAlphaAnimator.current + if chartsAlpha == 0 { return } + let range = renderRange(bounds: bounds, chartFrame: chartFrame) + + for (index, toLine) in toLines.enumerated() { + let alpha = linesAlphaAnimators[index].current * chartsAlpha + if alpha == 0 { continue } + context.setStrokeColor(toLine.color.withAlphaComponent(alpha).cgColor) + context.setLineWidth(lineWidth) + + if linesShapeAnimator.isAnimating { + let animationOffset = linesShapeAnimator.current + + let path = CGMutablePath() + let fromPoints = fromLines.safeElement(at: index)?.points ?? [] + let toPoints = toLines.safeElement(at: index)?.points ?? [] + + var fromIndex: Int? = fromPoints.firstIndex(where: { $0.x >= range.lowerBound }) + var toIndex: Int? = toPoints.firstIndex(where: { $0.x >= range.lowerBound }) + + let fromRange = verticalRange.start + let currentRange = verticalRange.current + let toRange = verticalRange.end + + func convertFromPoint(_ fromPoint: CGPoint) -> CGPoint { + return CGPoint(x: fromPoint.x, + y: (fromPoint.y - fromRange.lowerBound) / fromRange.distance * currentRange.distance + currentRange.lowerBound) + } + + func convertToPoint(_ toPoint: CGPoint) -> CGPoint { + return CGPoint(x: toPoint.x, + y: (toPoint.y - toRange.lowerBound) / toRange.distance * currentRange.distance + currentRange.lowerBound) + } + + var previousFromPoint: CGPoint + var previousToPoint: CGPoint + let startFromPoint: CGPoint? + let startToPoint: CGPoint? + + if let validFrom = fromIndex { + previousFromPoint = convertFromPoint(fromPoints[max(0, validFrom - 1)]) + startFromPoint = previousFromPoint + } else { + previousFromPoint = .zero + startFromPoint = nil + } + if let validTo = toIndex { + previousToPoint = convertToPoint(toPoints[max(0, validTo - 1)]) + startToPoint = previousToPoint + } else { + previousToPoint = .zero + startToPoint = nil + } + + var combinedPoints: [CGPoint] = [] + + func add(pointToDraw: CGPoint) { + if let startFromPoint = startFromPoint, + pointToDraw.x < startFromPoint.x { + let animatedPoint = CGPoint(x: pointToDraw.x, + y: CGFloat.valueBetween(start: startFromPoint.y, end: pointToDraw.y, offset: animationOffset)) + combinedPoints.append(transform(toChartCoordinate: animatedPoint, chartFrame: chartFrame)) + } else if let startToPoint = startToPoint, + pointToDraw.x < startToPoint.x { + let animatedPoint = CGPoint(x: pointToDraw.x, + y: CGFloat.valueBetween(start: startToPoint.y, end: pointToDraw.y, offset: 1 - animationOffset)) + combinedPoints.append(transform(toChartCoordinate: animatedPoint, chartFrame: chartFrame)) + } else { + combinedPoints.append(transform(toChartCoordinate: pointToDraw, chartFrame: chartFrame)) + } + } + + if previousToPoint != .zero && previousFromPoint != .zero { + add(pointToDraw: (previousToPoint.x < previousFromPoint.x ? previousToPoint : previousFromPoint)) + } else if previousToPoint != .zero { + add(pointToDraw: previousToPoint) + } else if previousFromPoint != .zero { + add(pointToDraw: previousFromPoint) + } + + while let validFromIndex = fromIndex, + let validToIndex = toIndex, + validFromIndex < fromPoints.count, + validToIndex < toPoints.count { + let currentFromPoint = convertFromPoint(fromPoints[validFromIndex]) + let currentToPoint = convertToPoint(toPoints[validToIndex]) + let pointToAdd: CGPoint + if currentFromPoint.x == currentToPoint.x { + pointToAdd = CGPoint.valueBetween(start: currentFromPoint, end: currentToPoint, offset: animationOffset) + previousFromPoint = currentFromPoint + previousToPoint = currentToPoint + fromIndex = validFromIndex + 1 + toIndex = validToIndex + 1 + } else if currentFromPoint.x < currentToPoint.x { + if previousToPoint.x < currentFromPoint.x { + let offset = Double((currentFromPoint.x - previousToPoint.x) / (currentToPoint.x - previousToPoint.x)) + let intermidiateToPoint = CGPoint.valueBetween(start: previousToPoint, end: currentToPoint, offset: offset) + pointToAdd = CGPoint.valueBetween(start: currentFromPoint, end: intermidiateToPoint, offset: animationOffset) + } else { + pointToAdd = currentFromPoint + } + previousFromPoint = currentFromPoint + fromIndex = validFromIndex + 1 + } else { + if previousFromPoint.x < currentToPoint.x { + let offset = Double((currentToPoint.x - previousFromPoint.x) / (currentFromPoint.x - previousFromPoint.x)) + let intermidiateFromPoint = CGPoint.valueBetween(start: previousFromPoint, end: currentFromPoint, offset: offset) + pointToAdd = CGPoint.valueBetween(start: intermidiateFromPoint, end: currentToPoint, offset: animationOffset) + } else { + pointToAdd = currentToPoint + } + previousToPoint = currentToPoint + toIndex = validToIndex + 1 + } + add(pointToDraw: pointToAdd) + if (pointToAdd.x > range.upperBound) { + break + } + } + + while let validToIndex = toIndex, validToIndex < toPoints.count { + var pointToAdd = convertToPoint(toPoints[validToIndex]) + pointToAdd.y = CGFloat.valueBetween(start: previousFromPoint.y, + end: pointToAdd.y, + offset: animationOffset) + + add(pointToDraw: pointToAdd) + if (pointToAdd.x > range.upperBound) { + break + } + + toIndex = validToIndex + 1 + } + + while let validFromIndex = fromIndex, validFromIndex < fromPoints.count { + var pointToAdd = convertFromPoint(fromPoints[validFromIndex]) + pointToAdd.y = CGFloat.valueBetween(start: previousToPoint.y, + end: pointToAdd.y, + offset: 1 - animationOffset) + + add(pointToDraw: pointToAdd) + if (pointToAdd.x > range.upperBound) { + break + } + + fromIndex = validFromIndex + 1 + } + + var index = 0 + var lines: [CGPoint] = [] + var currentChartPoint = combinedPoints[index] + lines.append(currentChartPoint) + + var chartPoints = [currentChartPoint] + var minIndex = 0 + var maxIndex = 0 + index += 1 + + while index < combinedPoints.count { + currentChartPoint = combinedPoints[index] + + if currentChartPoint.x - chartPoints[0].x < lineWidth * optimizationLevel { + chartPoints.append(currentChartPoint) + + if currentChartPoint.y > chartPoints[maxIndex].y { + maxIndex = chartPoints.count - 1 + } + if currentChartPoint.y < chartPoints[minIndex].y { + minIndex = chartPoints.count - 1 + } + + index += 1 + } else { + if chartPoints.count == 1 { + lines.append(currentChartPoint) + lines.append(currentChartPoint) + chartPoints[0] = currentChartPoint + index += 1 + minIndex = 0 + maxIndex = 0 + } else { + if minIndex < maxIndex { + if minIndex != 0 { + lines.append(chartPoints[minIndex]) + lines.append(chartPoints[minIndex]) + } + lines.append(chartPoints[maxIndex]) + lines.append(chartPoints[maxIndex]) + if maxIndex != chartPoints.count - 1 { + chartPoints = [chartPoints[maxIndex], chartPoints.last!] + } else { + chartPoints = [chartPoints[maxIndex]] + } + } else { + if maxIndex != 0 { + lines.append(chartPoints[maxIndex]) + lines.append(chartPoints[maxIndex]) + } + lines.append(chartPoints[minIndex]) + lines.append(chartPoints[minIndex]) + if minIndex != chartPoints.count - 1 { + chartPoints = [chartPoints[minIndex], chartPoints.last!] + } else { + chartPoints = [chartPoints[minIndex]] + } + } + if chartPoints.count == 2 { + if chartPoints[0].y < chartPoints[1].y { + minIndex = 0 + maxIndex = 1 + } else { + minIndex = 1 + maxIndex = 0 + } + } else { + minIndex = 0 + maxIndex = 0 + } + } + } + } + + if chartPoints.count == 1 { + lines.append(currentChartPoint) + lines.append(currentChartPoint) + } else { + if minIndex < maxIndex { + if minIndex != 0 { + lines.append(chartPoints[minIndex]) + lines.append(chartPoints[minIndex]) + } + lines.append(chartPoints[maxIndex]) + lines.append(chartPoints[maxIndex]) + if maxIndex != chartPoints.count - 1 { + lines.append(chartPoints.last!) + lines.append(chartPoints.last!) + } + } else { + if maxIndex != 0 { + lines.append(chartPoints[maxIndex]) + lines.append(chartPoints[maxIndex]) + } + lines.append(chartPoints[minIndex]) + lines.append(chartPoints[minIndex]) + if minIndex != chartPoints.count - 1 { + lines.append(chartPoints.last!) + lines.append(chartPoints.last!) + } + } + } + + if (lines.count % 2) == 1 { + lines.removeLast() + } + + context.setLineCap(.round) + context.strokeLineSegments(between: lines) + + } else { + let alpha = linesAlphaAnimators[index].current * chartsAlpha + if alpha == 0 { continue } + context.setStrokeColor(toLine.color.withAlphaComponent(alpha).cgColor) + context.setLineWidth(lineWidth) + + if var index = toLine.points.firstIndex(where: { $0.x >= range.lowerBound }) { + var lines: [CGPoint] = [] + index = max(0, index - 1) + var currentPoint = toLine.points[index] + var currentChartPoint = transform(toChartCoordinate: currentPoint, chartFrame: chartFrame) + lines.append(currentChartPoint) + //context.move(to: currentChartPoint) + + var chartPoints = [currentChartPoint] + var minIndex = 0 + var maxIndex = 0 + index += 1 + + while index < toLine.points.count { + currentPoint = toLine.points[index] + currentChartPoint = transform(toChartCoordinate: currentPoint, chartFrame: chartFrame) + + if currentChartPoint.x - chartPoints[0].x < lineWidth * optimizationLevel { + chartPoints.append(currentChartPoint) + + if currentChartPoint.y > chartPoints[maxIndex].y { + maxIndex = chartPoints.count - 1 + } + if currentChartPoint.y < chartPoints[minIndex].y { + minIndex = chartPoints.count - 1 + } + + index += 1 + } else { + if chartPoints.count == 1 { + lines.append(currentChartPoint) + lines.append(currentChartPoint) + chartPoints[0] = currentChartPoint + index += 1 + minIndex = 0 + maxIndex = 0 + } else { + if minIndex < maxIndex { + if minIndex != 0 { + lines.append(chartPoints[minIndex]) + lines.append(chartPoints[minIndex]) + } + lines.append(chartPoints[maxIndex]) + lines.append(chartPoints[maxIndex]) + if maxIndex != chartPoints.count - 1 { + chartPoints = [chartPoints[maxIndex], chartPoints.last!] + } else { + chartPoints = [chartPoints[maxIndex]] + } + } else { + if maxIndex != 0 { + lines.append(chartPoints[maxIndex]) + lines.append(chartPoints[maxIndex]) + } + lines.append(chartPoints[minIndex]) + lines.append(chartPoints[minIndex]) + if minIndex != chartPoints.count - 1 { + chartPoints = [chartPoints[minIndex], chartPoints.last!] + } else { + chartPoints = [chartPoints[minIndex]] + } + } + if chartPoints.count == 2 { + if chartPoints[0].y < chartPoints[1].y { + minIndex = 0 + maxIndex = 1 + } else { + minIndex = 1 + maxIndex = 0 + } + } else { + minIndex = 0 + maxIndex = 0 + } + } + } + if currentPoint.x > range.upperBound { + break + } + } + + if chartPoints.count == 1 { + lines.append(currentChartPoint) + lines.append(currentChartPoint) + } else { + if minIndex < maxIndex { + if minIndex != 0 { + lines.append(chartPoints[minIndex]) + lines.append(chartPoints[minIndex]) + } + lines.append(chartPoints[maxIndex]) + lines.append(chartPoints[maxIndex]) + if maxIndex != chartPoints.count - 1 { + lines.append(chartPoints.last!) + lines.append(chartPoints.last!) + } + } else { + if maxIndex != 0 { + lines.append(chartPoints[maxIndex]) + lines.append(chartPoints[maxIndex]) + } + lines.append(chartPoints[minIndex]) + lines.append(chartPoints[minIndex]) + if minIndex != chartPoints.count - 1 { + lines.append(chartPoints.last!) + lines.append(chartPoints.last!) + } + } + } + + if (lines.count % 2) == 1 { + lines.removeLast() + } + + context.setLineCap(.round) + context.strokeLineSegments(between: lines) + } + +// if var start = toLine.points.firstIndex(where: { $0.x > range.lowerBound }) { +// let alpha = linesAlphaAnimators[index].current * chartsAlpha +// if alpha == 0 { continue } +// context.setStrokeColor(toLine.color.withAlphaComponent(alpha).cgColor) +// context.setLineWidth(lineWidth) +// +// context.setLineCap(.round) +// start = max(0, start - 1) +// let startPoint = toLine.points[start] +// var lines: [CGPoint] = [] +// var pointToDraw = CGPoint(x: transform(toChartCoordinateHorizontal: startPoint.x, chartFrame: chartFrame), +// y: transform(toChartCoordinateVertical: startPoint.y, chartFrame: chartFrame)) +// for index in (start + 1).. range.upperBound { +// break +// } +// } +// +// context.strokeLineSegments(between: lines) +// } + } + } + } +} + +extension LinesChartRenderer.LineData { + static func initialComponents(chartsCollection: ChartsCollection) -> (linesData: [LinesChartRenderer.LineData], + totalHorizontalRange: ClosedRange, + totalVerticalRange: ClosedRange) { + let lines: [LinesChartRenderer.LineData] = chartsCollection.chartValues.map { chart in + let points = chart.values.enumerated().map({ (arg) -> CGPoint in + return CGPoint(x: chartsCollection.axisValues[arg.offset].timeIntervalSince1970, + y: arg.element) + }) + return LinesChartRenderer.LineData(color: chart.color, points: points) + } + let horizontalRange = LinesChartRenderer.LineData.horizontalRange(lines: lines) ?? BaseConstants.defaultRange + let verticalRange = LinesChartRenderer.LineData.verticalRange(lines: lines) ?? BaseConstants.defaultRange + return (linesData: lines, totalHorizontalRange: horizontalRange, totalVerticalRange: verticalRange) + } + + static func horizontalRange(lines: [LinesChartRenderer.LineData]) -> ClosedRange? { + guard let firstPoint = lines.first?.points.first else { return nil } + var hMin: CGFloat = firstPoint.x + var hMax: CGFloat = firstPoint.x + + for line in lines { + if let first = line.points.first, + let last = line.points.last { + hMin = min(hMin, first.x) + hMax = max(hMax, last.x) + } + } + + return hMin...hMax + } + + static func verticalRange(lines: [LinesChartRenderer.LineData], calculatingRange: ClosedRange? = nil, addBounds: Bool = false) -> ClosedRange? { + if let calculatingRange = calculatingRange { + guard let initalStart = lines.first?.points.first(where: { $0.x >= calculatingRange.lowerBound && + $0.x <= calculatingRange.upperBound }) else { return nil } + var vMin: CGFloat = initalStart.y + var vMax: CGFloat = initalStart.y + for line in lines { + if var index = line.points.firstIndex(where: { $0.x > calculatingRange.lowerBound }) { + if addBounds { + index = max(0, index - 1) + } + while index < line.points.count { + let point = line.points[index] + if point.x < calculatingRange.upperBound { + vMin = min(vMin, point.y) + vMax = max(vMax, point.y) + } else if addBounds { + vMin = min(vMin, point.y) + vMax = max(vMax, point.y) + break + } else { + break + } + index += 1 + } + } + } + return vMin...vMax + } else { + guard let firstPoint = lines.first?.points.first else { return nil } + var vMin: CGFloat = firstPoint.y + var vMax: CGFloat = firstPoint.y + for line in lines { + for point in line.points { + vMin = min(vMin, point.y) + vMax = max(vMax, point.y) + } + } + return vMin...vMax + } + } +} diff --git a/submodules/GraphCore/src/Charts/Renderes/PecentChartRenderer.swift b/submodules/GraphCore/src/Charts/Renderes/PecentChartRenderer.swift new file mode 100644 index 0000000000..7c402941b2 --- /dev/null +++ b/submodules/GraphCore/src/Charts/Renderes/PecentChartRenderer.swift @@ -0,0 +1,137 @@ +// +// PecentChartRenderer.swift +// GraphTest +// +// Created by Andrei Salavei on 4/7/19. +// Copyright © 2019 Andrei Salavei. All rights reserved. +// + +import Foundation +#if os(macOS) +import Cocoa +#else +import UIKit +#endif + +class PecentChartRenderer: BaseChartRenderer { + struct PercentageData { + static let blank = PecentChartRenderer.PercentageData(locations: [], components: []) + var locations: [CGFloat] + var components: [Component] + + struct Component { + var color: GColor + var values: [CGFloat] + } + } + + override func setup(verticalRange: ClosedRange, animated: Bool, timeFunction: TimeFunction? = nil) { + super.setup(verticalRange: 0...1, animated: animated, timeFunction: timeFunction) + } + + private var componentsAnimators: [AnimationController] = [] + var percentageData: PercentageData = PercentageData(locations: [], components: []) { + willSet { + if percentageData.components.count != newValue.components.count { + componentsAnimators = newValue.components.map { _ in AnimationController(current: 1, refreshClosure: self.refreshClosure) } + } + } + didSet { + setNeedsDisplay() + } + } + + func setComponentVisible(_ isVisible: Bool, at index: Int, animated: Bool) { + componentsAnimators[index].animate(to: isVisible ? 1 : 0, duration: animated ? .defaultDuration : 0) + } + + override func render(context: CGContext, bounds: CGRect, chartFrame: CGRect) { + guard isEnabled && verticalRange.current.distance > 0 && verticalRange.current.distance > 0 else { return } + let alpha = chartAlphaAnimator.current + guard alpha > 0 else { return } + + let range = renderRange(bounds: bounds, chartFrame: chartFrame) + + var paths: [CGMutablePath] = percentageData.components.map { _ in CGMutablePath() } + var vertices: [CGFloat] = Array(repeating: 0, count: percentageData.components.count) + + if var locationIndex = percentageData.locations.firstIndex(where: { $0 > range.lowerBound }) { + locationIndex = max(0, locationIndex - 1) + + var currentLocation = transform(toChartCoordinateHorizontal: percentageData.locations[locationIndex], chartFrame: chartFrame) + + let startPoint = CGPoint(x: currentLocation, + y: transform(toChartCoordinateVertical: verticalRange.current.lowerBound, chartFrame: chartFrame)) + + for path in paths { + path.move(to: startPoint) + } + paths.last?.addLine(to: CGPoint(x: currentLocation, + y: transform(toChartCoordinateVertical: verticalRange.current.upperBound, chartFrame: chartFrame))) + + while locationIndex < percentageData.locations.count { + currentLocation = transform(toChartCoordinateHorizontal: percentageData.locations[locationIndex], chartFrame: chartFrame) + var summ: CGFloat = 0 + + for (index, component) in percentageData.components.enumerated() { + let visibilityPercent = componentsAnimators[index].current + + let value = component.values[locationIndex] * visibilityPercent + if index == 0 { + vertices[index] = value + } else { + vertices[index] = value + vertices[index - 1] + } + summ += value + } + + if summ > 0 { + for (index, value) in vertices.dropLast().enumerated() { + paths[index].addLine(to: CGPoint(x: currentLocation, + y: transform(toChartCoordinateVertical: value / summ, chartFrame: chartFrame))) + } + } + + if currentLocation > range.upperBound { + break + } + + locationIndex += 1 + } + + paths.last?.addLine(to: CGPoint(x: currentLocation, + y: transform(toChartCoordinateVertical: verticalRange.current.upperBound, chartFrame: chartFrame))) + + let endPoint = CGPoint(x: currentLocation, + y: transform(toChartCoordinateVertical: verticalRange.current.lowerBound, chartFrame: chartFrame)) + + for (index, path) in paths.enumerated().reversed() { + let visibilityPercent = componentsAnimators[index].current + if visibilityPercent == 0 { continue } + + path.addLine(to: endPoint) + path.closeSubpath() + + context.saveGState() + context.beginPath() + context.addPath(path) + + context.setFillColor(percentageData.components[index].color.cgColor) + context.fillPath() + context.restoreGState() + } + } + } +} + +extension PecentChartRenderer.PercentageData { + static func horizontalRange(data: PecentChartRenderer.PercentageData) -> ClosedRange? { + guard let firstPoint = data.locations.first, + let lastPoint = data.locations.last, + firstPoint <= lastPoint else { + return nil + } + + return firstPoint...lastPoint + } +} diff --git a/submodules/GraphCore/src/Charts/Renderes/PercentPieAnimationRenderer.swift b/submodules/GraphCore/src/Charts/Renderes/PercentPieAnimationRenderer.swift new file mode 100644 index 0000000000..dfa0ff3c97 --- /dev/null +++ b/submodules/GraphCore/src/Charts/Renderes/PercentPieAnimationRenderer.swift @@ -0,0 +1,207 @@ +// +// PercentPieAnimationRenderer.swift +// GraphTest +// +// Created by Andrei Salavei on 4/13/19. +// Copyright © 2019 Andrei Salavei. All rights reserved. +// + +import Foundation +#if os(macOS) +import Cocoa +#else +import UIKit +#endif + +class PercentPieAnimationRenderer: BaseChartRenderer { + override func setup(verticalRange: ClosedRange, animated: Bool, timeFunction: TimeFunction? = nil) { + super.setup(verticalRange: 0...1, animated: animated, timeFunction: timeFunction) + } + + private lazy var transitionAnimator = AnimationController(current: 0, refreshClosure: refreshClosure) + private var animationComponentsPoints: [[CGPoint]] = [] + var visiblePercentageData: PecentChartRenderer.PercentageData = .blank { + didSet { + animationComponentsPoints = [] + } + } + var visiblePieComponents: [PieChartRenderer.PieComponent] = [] + + func animate(fromDataToPie: Bool, animated: Bool, completion: @escaping () -> Void) { + assert(visiblePercentageData.components.count == visiblePieComponents.count) + + isEnabled = true + transitionAnimator.completionClosure = { [weak self] in + self?.isEnabled = false + completion() + } + transitionAnimator.animate(to: fromDataToPie ? 1 : 0, duration: animated ? .defaultDuration : 0) + } + + private func generateAnimationComponentPoints(bounds: CGRect, chartFrame: CGRect) { + let range = renderRange(bounds: bounds, chartFrame: chartFrame) + + let componentsCount = visiblePercentageData.components.count + guard componentsCount > 0 else { return } + animationComponentsPoints = visiblePercentageData.components.map { _ in [] } + var vertices: [CGFloat] = Array(repeating: 0, count: visiblePercentageData.components.count) + + if var locationIndex = visiblePercentageData.locations.firstIndex(where: { $0 > range.lowerBound }) { + locationIndex = max(0, locationIndex - 1) + var currentLocation = transform(toChartCoordinateHorizontal: visiblePercentageData.locations[locationIndex], chartFrame: chartFrame) + let startPoint = CGPoint(x: currentLocation, y: transform(toChartCoordinateVertical: verticalRange.current.lowerBound, chartFrame: chartFrame)) + for index in 0.. range.upperBound { + break + } + locationIndex += 1 + } + + animationComponentsPoints[componentsCount - 1].append(CGPoint(x: currentLocation, y: transform(toChartCoordinateVertical: verticalRange.current.upperBound, chartFrame: chartFrame))) + let endPoint = CGPoint(x: currentLocation, y: transform(toChartCoordinateVertical: verticalRange.current.lowerBound, chartFrame: chartFrame)) + for index in 0.. 0 && verticalRange.current.distance > 0 else { return } + self.optimizationLevel = 1 + + if animationComponentsPoints.isEmpty { + generateAnimationComponentPoints(bounds: bounds, chartFrame: chartFrame) + } + + let numberOfComponents = animationComponentsPoints.count + guard numberOfComponents > 0 else { return } + let destinationRadius = max(chartFrame.width, chartFrame.height) + + let animationFraction = transitionAnimator.current + let animationFractionD = Double(transitionAnimator.current) + let easeInAnimationFractionD = animationFractionD * animationFractionD * animationFractionD * animationFractionD + let center = CGPoint(x: chartFrame.midX, y: chartFrame.midY) + let totalPieSumm: CGFloat = visiblePieComponents.map { $0.value } .reduce(0, +) + + let pathsToDraw: [CGMutablePath] = (0.. 4 else { + return + } + + let percent = visiblePieComponents[componentIndex].value / totalPieSumm + let segmentSize = 2 * .pi * percent + let endAngle = startAngle + segmentSize + let centerAngle = (startAngle + endAngle) / 2 + + let lineCenterPoint = CGPoint.valueBetween(start: componentPoints[componentPoints.count / 2], + end: center, + offset: animationFractionD) + + let startDestinationPoint = lineCenterPoint + CGPoint(x: destinationRadius, y: 0) + let centerDestinationPoint = lineCenterPoint + CGPoint(x: 0, y: destinationRadius) + let endDestinationPoint = lineCenterPoint + CGPoint(x: -destinationRadius, y: 0) + let initialStartDestinationAngle: CGFloat = 0 + let initialCenterDestinationAngle: CGFloat = .pi / 2 + let initialEndDestinationAngle: CGFloat = .pi + + var previousAddedPoint = (componentPoints[0] * 2 - center) + .rotate(origin: lineCenterPoint, angle: CGFloat.valueBetween(start: 0, end: centerAngle - initialCenterDestinationAngle, offset: animationFractionD)) + + pathsToDraw[componentIndex].move(to: previousAddedPoint) + + func addPointToPath(_ point: CGPoint) { + if (point - previousAddedPoint).lengthSquared() > optimizationLevel { + pathsToDraw[componentIndex].addLine(to: point) + previousAddedPoint = point + } + } + + for endPointIndex in 1..<(componentPoints.count / 2) { + addPointToPath(CGPoint.valueBetween(start: componentPoints[endPointIndex], end: endDestinationPoint, offset: easeInAnimationFractionD) + .rotate(origin: lineCenterPoint, angle: CGFloat.valueBetween(start: 0, end: endAngle - initialEndDestinationAngle, offset: animationFractionD))) + } + + addPointToPath(lineCenterPoint) + + for startPointIndex in (componentPoints.count / 2 + 1)..<(componentPoints.count - 1) { + addPointToPath(CGPoint.valueBetween(start: componentPoints[startPointIndex], end: startDestinationPoint, offset: easeInAnimationFractionD) + .rotate(origin: lineCenterPoint, angle: CGFloat.valueBetween(start: 0, end: startAngle - initialStartDestinationAngle, offset: animationFractionD))) + } + + if let lastPoint = componentPoints.last { + addPointToPath((lastPoint * 2 - center) + .rotate(origin: lineCenterPoint, angle: CGFloat.valueBetween(start: 0, end: centerAngle - initialCenterDestinationAngle, offset: animationFractionD))) + } + + startAngle = endAngle + } + + if let lastPath = animationComponentsPoints.last { + pathsToDraw.last?.addLines(between: lastPath) + } + + for (index, path) in pathsToDraw.enumerated().reversed() { + path.closeSubpath() + + context.saveGState() + context.beginPath() + context.addPath(path) + + context.setFillColor(visiblePieComponents[index].color.cgColor) + context.fillPath() + context.restoreGState() + } + + let diagramRadius = (min(chartFrame.width, chartFrame.height) / 2) * 0.925 + let targetFrame = CGRect(origin: CGPoint(x: center.x - diagramRadius, + y: center.y - diagramRadius), + size: CGSize(width: diagramRadius * 2, + height: diagramRadius * 2)) + + let minX = animationComponentsPoints.last?.first?.x ?? 0 + let maxX = animationComponentsPoints.last?.last?.x ?? 0 + let startFrame = CGRect(x: minX, + y: chartFrame.minY, + width: maxX - minX, + height: chartFrame.height) + let cornerRadius = diagramRadius * animationFraction + let fadeOutFrame = CGRect.valueBetween(start: startFrame, end: targetFrame, offset: animationFractionD) + let fadeOutPath = CGMutablePath() + fadeOutPath.addRect(bounds) + fadeOutPath.addPath(CGPath(roundedRect: fadeOutFrame, cornerWidth: cornerRadius, cornerHeight: cornerRadius, transform: nil)) + + context.saveGState() + context.beginPath() + context.addPath(fadeOutPath) + context.setFillColor(backgroundColor.cgColor) + context.fillPath(using: .evenOdd) + context.restoreGState() + } +} diff --git a/submodules/GraphCore/src/Charts/Renderes/PerformanceRenderer.swift b/submodules/GraphCore/src/Charts/Renderes/PerformanceRenderer.swift new file mode 100644 index 0000000000..572ef5c68d --- /dev/null +++ b/submodules/GraphCore/src/Charts/Renderes/PerformanceRenderer.swift @@ -0,0 +1,36 @@ +// +// PerformanceRenderer.swift +// GraphTest +// +// Created by Andrei Salavei on 4/10/19. +// Copyright © 2019 Andrei Salavei. All rights reserved. +// + +import Foundation +#if os(macOS) +import Cocoa +#else +import UIKit +#endif + +class PerformanceRenderer: ChartViewRenderer { + var containerViews: [GView] = [] + + private var previousTickTime: TimeInterval = CACurrentMediaTime() + + func render(context: CGContext, bounds: CGRect, chartFrame: CGRect) { + let currentTime = CACurrentMediaTime() + let delta = currentTime - previousTickTime + previousTickTime = currentTime + + let normalDelta = 0.017 + let redDelta = 0.05 + + if delta > normalDelta || delta < 0.75 { + let green = CGFloat( 1.0 - crop(0, (delta - normalDelta) / (redDelta - normalDelta), 1)) + let color = GColor(red: 1.0, green: green, blue: 0, alpha: 1) + context.setFillColor(color.cgColor) + context.fill(CGRect(x: 0, y: 0, width: bounds.width, height: 3)) + } + } +} diff --git a/submodules/GraphCore/src/Charts/Renderes/PieChartRenderer.swift b/submodules/GraphCore/src/Charts/Renderes/PieChartRenderer.swift new file mode 100644 index 0000000000..d0e06ed2ff --- /dev/null +++ b/submodules/GraphCore/src/Charts/Renderes/PieChartRenderer.swift @@ -0,0 +1,196 @@ +// +// PieChartRenderer.swift +// GraphTest +// +// Created by Andrei Salavei on 4/11/19. +// Copyright © 2019 Andrei Salavei. All rights reserved. +// + +import Foundation +#if os(macOS) +import Cocoa +#else +import UIKit +#endif + +class PieChartRenderer: BaseChartRenderer { + struct PieComponent: Hashable { + var color: GColor + var value: CGFloat + } + + override func setup(verticalRange: ClosedRange, animated: Bool, timeFunction: TimeFunction? = nil) { + super.setup(verticalRange: 0...1, animated: animated, timeFunction: timeFunction) + } + + var valuesFormatter: NumberFormatter = NumberFormatter() + var drawValues: Bool = true + + private var componentsAnimators: [AnimationController] = [] + private lazy var transitionAnimator: AnimationController = { AnimationController(current: 1, refreshClosure: self.refreshClosure) }() + private var oldPercentageData: [PieComponent] = [] + private var percentageData: [PieComponent] = [] + private var setlectedSegmentsAnimators: [AnimationController] = [] + + var drawPie: Bool = true + var initialAngle: CGFloat = .pi / 3 + var hasSelectedSegments: Bool { + return selectedSegment != nil + } + private(set) var selectedSegment: Int? + func selectSegmentAt(at indexToSelect: Int?, animated: Bool) { + selectedSegment = indexToSelect + for (index, animator) in setlectedSegmentsAnimators.enumerated() { + let fraction: CGFloat = (index == indexToSelect) ? 1.0 : 0.0 + if animated { + animator.animate(to: fraction, duration: .defaultDuration / 2) + } else { + animator.set(current: fraction) + } + } + } + + func updatePercentageData(_ percentageData: [PieComponent], animated: Bool) { + if self.percentageData.count != percentageData.count { + componentsAnimators = percentageData.map { _ in AnimationController(current: 1, refreshClosure: self.refreshClosure) } + setlectedSegmentsAnimators = percentageData.map { _ in AnimationController(current: 0, refreshClosure: self.refreshClosure) } + } + if animated { + self.oldPercentageData = self.currentTransitionAnimationData + self.percentageData = percentageData + transitionAnimator.completionClosure = { [weak self] in + self?.oldPercentageData = [] + } + transitionAnimator.set(current: 0) + transitionAnimator.animate(to: 1, duration: .defaultDuration) + } else { + self.oldPercentageData = [] + self.percentageData = percentageData + transitionAnimator.set(current: 0) + } + } + + func setComponentVisible(_ isVisible: Bool, at index: Int, animated: Bool) { + componentsAnimators[index].animate(to: isVisible ? 1 : 0, duration: animated ? .defaultDuration : 0) + } + + var lastRenderedBounds: CGRect = .zero + var lastRenderedChartFrame: CGRect = .zero + func selectedItemIndex(at point: CGPoint) -> Int? { + let touchPosition = lastRenderedChartFrame.origin + point * lastRenderedChartFrame.size + let center = CGPoint(x: lastRenderedChartFrame.midX, y: lastRenderedChartFrame.midY) + let radius = min(lastRenderedChartFrame.width, lastRenderedChartFrame.height) / 2 + if center.distanceTo(touchPosition) > radius { return nil } + let angle = (center - touchPosition).angle + .pi + let currentData = currentlyVisibleData + let total: CGFloat = currentData.map({ $0.value }).reduce(0, +) + var startAngle: CGFloat = initialAngle + for (index, piece) in currentData.enumerated() { + let percent = piece.value / total + let segmentSize = 2 * .pi * percent + let endAngle = startAngle + segmentSize + if angle >= startAngle && angle <= endAngle || + angle + .pi * 2 >= startAngle && angle + .pi * 2 <= endAngle { + return index + } + startAngle = endAngle + } + return nil + } + + private var currentTransitionAnimationData: [PieComponent] { + if transitionAnimator.isAnimating { + let animationFraction = transitionAnimator.current + return percentageData.enumerated().map { arg in + return PieComponent(color: arg.element.color, + value: oldPercentageData[arg.offset].value * (1 - animationFraction) + arg.element.value * animationFraction) + } + } else { + return percentageData + } + } + + var currentlyVisibleData: [PieComponent] { + return currentTransitionAnimationData.enumerated().map { arg in + return PieComponent(color: arg.element.color, + value: arg.element.value * componentsAnimators[arg.offset].current) + } + } + + override func render(context: CGContext, bounds: CGRect, chartFrame: CGRect) { + guard isEnabled && verticalRange.current.distance > 0 && verticalRange.current.distance > 0 else { return } + lastRenderedBounds = bounds + lastRenderedChartFrame = chartFrame + let chartAlpha = chartAlphaAnimator.current + if chartAlpha == 0 { return } + + let center = CGPoint(x: chartFrame.midX, y: chartFrame.midY) + let radius = min(chartFrame.width, chartFrame.height) / 2 + + let currentData = currentlyVisibleData + + let total: CGFloat = currentData.map({ $0.value }).reduce(0, +) + guard total > 0 else { + return + } + + let animationSelectionOffset: CGFloat = radius / 15 + let maximumFontSize: CGFloat = radius / 7 + let minimumFontSize: CGFloat = 4 + let centerOffsetStartAngle = CGFloat.pi / 4 + let minimumValueToDraw: CGFloat = 0.01 + let diagramRadius = radius - animationSelectionOffset + + let numberOfVisibleItems = currentlyVisibleData.filter { $0.value > 0 }.count + var startAngle: CGFloat = initialAngle + for (index, piece) in currentData.enumerated() { + let percent = piece.value / total + guard percent > 0 else { continue } + let segmentSize = 2 * .pi * percent * chartAlpha + let endAngle = startAngle + segmentSize + let centerAngle = (startAngle + endAngle) / 2 + let labelVector = CGPoint(x: cos(centerAngle), + y: sin(centerAngle)) + + let selectionAnimationFraction = (numberOfVisibleItems > 1 ? setlectedSegmentsAnimators[index].current : 0) + + let updatedCenter = CGPoint(x: center.x + labelVector.x * selectionAnimationFraction * animationSelectionOffset, + y: center.y + labelVector.y * selectionAnimationFraction * animationSelectionOffset) + if drawPie { + context.saveGState() + context.setFillColor(piece.color.withAlphaComponent(piece.color.alphaValue * chartAlpha).cgColor) + context.move(to: updatedCenter) + context.addArc(center: updatedCenter, + radius: radius - animationSelectionOffset, + startAngle: startAngle, + endAngle: endAngle, + clockwise: false) + context.fillPath() + context.restoreGState() + } + + if drawValues && percent >= minimumValueToDraw { + context.saveGState() + + let text = valuesFormatter.string(from: percent * 100) + let fraction = crop(0, segmentSize / centerOffsetStartAngle, 1) + let fontSize = (minimumFontSize + (maximumFontSize - minimumFontSize) * fraction).rounded(.up) + let labelPotisionOffset = diagramRadius / 2 + diagramRadius / 2 * (1 - fraction) + let font = NSFont.systemFont(ofSize: fontSize, weight: .bold) + let labelsEaseInColor = crop(0, chartAlpha * chartAlpha * 2 - 1, 1) + let attributes: [NSAttributedString.Key: Any] = [.foregroundColor: GColor.white.withAlphaComponent(labelsEaseInColor), + .font: font] + + let attributedString = NSAttributedString(string: text, attributes: attributes) + let textNode = LabelNode.layoutText(attributedString, bounds.size) + + let labelPoint = CGPoint(x: labelVector.x * labelPotisionOffset + updatedCenter.x - textNode.0.size.width / 2, + y: labelVector.y * labelPotisionOffset + updatedCenter.y - textNode.0.size.height / 2) + textNode.1.draw(CGRect(origin: labelPoint, size: textNode.0.size), in: context, backingScaleFactor: deviceScale) + context.restoreGState() + } + + startAngle = endAngle + } + } +} diff --git a/submodules/GraphCore/src/Charts/Renderes/VerticalLinesRenderer.swift b/submodules/GraphCore/src/Charts/Renderes/VerticalLinesRenderer.swift new file mode 100644 index 0000000000..4fc169d44e --- /dev/null +++ b/submodules/GraphCore/src/Charts/Renderes/VerticalLinesRenderer.swift @@ -0,0 +1,47 @@ +// +// VerticalLinesRenderer.swift +// GraphTest +// +// Created by Andrei Salavei on 4/8/19. +// Copyright © 2019 Andrei Salavei. All rights reserved. +// + +import Foundation +#if os(macOS) +import Cocoa +#else +import UIKit +#endif + +class VerticalLinesRenderer: BaseChartRenderer { + var values: [CGFloat] = [] { + didSet { + alphaAnimators = values.map { _ in AnimationController(current: 1.0, refreshClosure: refreshClosure) } + setNeedsDisplay() + } + } + private var alphaAnimators: [AnimationController] = [] + + var linesColor: GColor = .black + var linesWidth: CGFloat = GView.oneDevicePixel + + func setLineVisible(_ isVisible: Bool, at index: Int, animated: Bool) { + alphaAnimators[index].animate(to: isVisible ? 1 : 0, duration: animated ? .defaultDuration : 0) + } + + override func render(context: CGContext, bounds: CGRect, chartFrame: CGRect) { + guard isEnabled && verticalRange.current.distance > 0 && verticalRange.current.distance > 0 else { return } + + context.setLineWidth(linesWidth) + + for (index, value) in values.enumerated() { + let alpha = alphaAnimators[index].current + if alpha == 0 { continue } + + context.setStrokeColor(linesColor.withAlphaComponent(linesColor.alphaValue * alpha).cgColor) + let pointX = transform(toChartCoordinateHorizontal: value, chartFrame: chartFrame) + context.strokeLineSegments(between: [CGPoint(x: pointX, y: chartFrame.minY), + CGPoint(x: pointX, y: chartFrame.maxY)]) + } + } +} diff --git a/submodules/GraphCore/src/Charts/Renderes/VerticalScalesRenderer.swift b/submodules/GraphCore/src/Charts/Renderes/VerticalScalesRenderer.swift new file mode 100644 index 0000000000..1408b12cd5 --- /dev/null +++ b/submodules/GraphCore/src/Charts/Renderes/VerticalScalesRenderer.swift @@ -0,0 +1,168 @@ +// +// VerticalScalesRenderer.swift +// GraphTest +// +// Created by Andrei Salavei on 4/8/19. +// Copyright © 2019 Andrei Salavei. All rights reserved. +// + +import Foundation +#if os(macOS) +import Cocoa +#else +import UIKit +#endif + +class VerticalScalesRenderer: BaseChartRenderer { + private var verticalLabelsAndLines: [LinesChartLabel] = [] + private var animatedVerticalLabelsAndLines: [AnimatedLinesChartLabels] = [] + private lazy var horizontalLinesAlphaAnimator: AnimationController = { + return AnimationController(current: 1, refreshClosure: self.refreshClosure) + }() + + var drawAxisX: Bool = true + var axisXColor: GColor = .black + var axisXWidth: CGFloat = GView.oneDevicePixel + + var isRightAligned: Bool = false + + var horizontalLinesColor: GColor = .black { + didSet { + setNeedsDisplay() + } + } + var horizontalLinesWidth: CGFloat = GView.oneDevicePixel + var lavelsAsisOffset: CGFloat = 6 + var labelsColor: GColor = .black { + didSet { + setNeedsDisplay() + } + } + var labelsFont: NSFont = .systemFont(ofSize: 11) + + func setHorizontalLinesVisible(_ visible: Bool, animated: Bool) { + let destinationValue: CGFloat = visible ? 1 : 0 + guard self.horizontalLinesAlphaAnimator.end != destinationValue else { return } + if animated { + self.horizontalLinesAlphaAnimator.animate(to: destinationValue, duration: .defaultDuration) + } else { + self.horizontalLinesAlphaAnimator.set(current: destinationValue) + } + } + + func setup(verticalLimitsLabels: [LinesChartLabel], animated: Bool) { + if animated { + var labelsToKeepVisible: [LinesChartLabel] = [] + let labelsToHide: [LinesChartLabel] + var labelsToShow: [LinesChartLabel] = [] + + for label in verticalLimitsLabels { + if verticalLabelsAndLines.contains(label) { + labelsToKeepVisible.append(label) + } else { + labelsToShow.append(label) + } + } + labelsToHide = verticalLabelsAndLines.filter { !verticalLimitsLabels.contains($0) } + animatedVerticalLabelsAndLines.removeAll(where: { $0.isAppearing }) + verticalLabelsAndLines = labelsToKeepVisible + + let showAnimation = AnimatedLinesChartLabels(labels: labelsToShow, alphaAnimator: AnimationController(current: 1.0, refreshClosure: refreshClosure)) + showAnimation.isAppearing = true + showAnimation.alphaAnimator.set(current: 0) + showAnimation.alphaAnimator.animate(to: 1, duration: .defaultDuration) + showAnimation.alphaAnimator.completionClosure = { [weak self, weak showAnimation] in + guard let self = self, let showAnimation = showAnimation else { return } + self.animatedVerticalLabelsAndLines.removeAll(where: { $0 === showAnimation }) + self.verticalLabelsAndLines = verticalLimitsLabels + } + + let hideAnimation = AnimatedLinesChartLabels(labels: labelsToHide, alphaAnimator: AnimationController(current: 1.0, refreshClosure: refreshClosure)) + hideAnimation.isAppearing = false + hideAnimation.alphaAnimator.set(current: 1) + hideAnimation.alphaAnimator.animate(to: 0, duration: .defaultDuration) + hideAnimation.alphaAnimator.completionClosure = { [weak self, weak hideAnimation] in + guard let self = self, let hideAnimation = hideAnimation else { return } + self.animatedVerticalLabelsAndLines.removeAll(where: { $0 === hideAnimation }) + } + + animatedVerticalLabelsAndLines.append(showAnimation) + animatedVerticalLabelsAndLines.append(hideAnimation) + } else { + verticalLabelsAndLines = verticalLimitsLabels + animatedVerticalLabelsAndLines = [] + } + } + + override func render(context: CGContext, bounds: CGRect, chartFrame: CGRect) { + guard isEnabled && verticalRange.current.distance > 0 && verticalRange.current.distance > 0 else { return } + let generalAlpha = chartAlphaAnimator.current + if generalAlpha == 0 { return } + let labelColorAlpha = labelsColor.alphaValue + + func drawLines(_ labels: [LinesChartLabel], alpha: CGFloat) { + var lineSegments: [CGPoint] = [] + let x0 = chartFrame.minX + let x1 = chartFrame.maxX + + context.setStrokeColor(horizontalLinesColor.withAlphaComponent(horizontalLinesColor.alphaValue * alpha).cgColor) + + for lineInfo in labels { + let y = transform(toChartCoordinateVertical: lineInfo.value, chartFrame: chartFrame).roundedUpToPixelGrid() + lineSegments.append(CGPoint(x: x0, y: y)) + lineSegments.append(CGPoint(x: x1, y: y)) + } + context.strokeLineSegments(between: lineSegments) + } + + func drawVerticalLabels(_ labels: [LinesChartLabel], attributes: [NSAttributedString.Key: Any]) { + if isRightAligned { + for label in labels { + let y = transform(toChartCoordinateVertical: label.value, chartFrame: chartFrame) - labelsFont.pointSize - lavelsAsisOffset + + + + + let attributedString = NSAttributedString(string: label.text, attributes: attributes) + let textNode = LabelNode.layoutText(attributedString, bounds.size) + textNode.1.draw(CGRect(origin: CGPoint(x:chartFrame.maxX - textNode.0.size.width, y: y), size: textNode.0.size), in: context, backingScaleFactor: deviceScale) + } + } else { + for label in labels { + let y = transform(toChartCoordinateVertical: label.value, chartFrame: chartFrame) - labelsFont.pointSize - lavelsAsisOffset + let attributedString = NSAttributedString(string: label.text, attributes: attributes) + let textNode = LabelNode.layoutText(attributedString, bounds.size) + textNode.1.draw(CGRect(origin: CGPoint(x:chartFrame.minX, y: y), size: textNode.0.size), in: context, backingScaleFactor: deviceScale) + } + } + } + + let horizontalLinesAlpha = horizontalLinesAlphaAnimator.current + if horizontalLinesAlpha > 0 { + context.setLineWidth(horizontalLinesWidth) + + drawLines(verticalLabelsAndLines, alpha: generalAlpha) + for animatedLabesAndLines in animatedVerticalLabelsAndLines { + drawLines(animatedLabesAndLines.labels, alpha: animatedLabesAndLines.alphaAnimator.current * generalAlpha * horizontalLinesAlpha) + } + + if drawAxisX { + context.setLineWidth(axisXWidth) + context.setStrokeColor(axisXColor.withAlphaComponent(axisXColor.alphaValue * horizontalLinesAlpha * generalAlpha).cgColor) + + let lineSegments: [CGPoint] = [CGPoint(x: chartFrame.minX, y: chartFrame.maxY.roundedUpToPixelGrid()), + CGPoint(x: chartFrame.maxX, y: chartFrame.maxY.roundedUpToPixelGrid())] + + context.strokeLineSegments(between: lineSegments) + } + } + + drawVerticalLabels(verticalLabelsAndLines, attributes: [.foregroundColor: labelsColor.withAlphaComponent(labelColorAlpha * generalAlpha), + .font: labelsFont]) + for animatedLabesAndLines in animatedVerticalLabelsAndLines { + drawVerticalLabels(animatedLabesAndLines.labels, + attributes: [.foregroundColor: labelsColor.withAlphaComponent(animatedLabesAndLines.alphaAnimator.current * labelColorAlpha * generalAlpha), + .font: labelsFont]) + } + } +} diff --git a/submodules/GraphCore/src/Helpers/AnimationController.swift b/submodules/GraphCore/src/Helpers/AnimationController.swift new file mode 100644 index 0000000000..7a45825ef0 --- /dev/null +++ b/submodules/GraphCore/src/Helpers/AnimationController.swift @@ -0,0 +1,183 @@ +// +// RangeAnimatedContainer.swift +// GraphTest +// +// Created by Andrei Salavei on 3/12/19. +// Copyright © 2019 Andrei Salavei. All rights reserved. +// + +import Foundation +#if os(macOS) +import Cocoa +#else +import UIKit +#endif + +protocol Animatable { + static func valueBetween(start: Self, end: Self, offset: Double) -> Self +} + +enum TimeFunction { + case linear + case easeOut + case easeIn + + func profress(time: TimeInterval, duration: TimeInterval) -> TimeInterval { + switch self { + case .linear: + return time / duration + case .easeIn: + return (pow(2, 10 * (time / duration - 1)) - 0.0009765625) * 1.0009775171065499 + + case .easeOut: + return (-pow(2, -10 * time / duration)) + 1 * 1.0009775171065499 + } + } +} + +class AnimationController { + + private(set) var isAnimating: Bool = false + private(set) var animationDuration: TimeInterval = 0.0 + private(set) var currentTime: TimeInterval = 0.0 + + private(set) var start: AnimatableObject + private(set) var end: AnimatableObject + private(set) var current: AnimatableObject + + var timeFunction: TimeFunction = .linear + + var refreshClosure: (() -> Void)? +// var updateClosure: ((AnimatableObject) -> Void)? + var completionClosure: (() -> Void)? + + init(current: AnimatableObject, refreshClosure: (() -> Void)?) { + self.current = current + self.start = current + self.end = current + self.refreshClosure = refreshClosure + } + + func animate(to: AnimatableObject, duration: TimeInterval, timeFunction: TimeFunction = .linear) { + self.timeFunction = timeFunction + currentTime = 0 + animationDuration = duration + if animationDuration > 0 { + start = current + end = to + isAnimating = true + DisplayLinkService.shared.add(listner: self) + } else { + start = to + end = to + current = to + isAnimating = false + DisplayLinkService.shared.remove(listner: self) + } + refreshClosure?() + } + + func set(current: AnimatableObject) { + self.start = current + self.end = current + self.current = current + + animationDuration = 0.0 + currentTime = 0.0 +// updateClosure?(current) + refreshClosure?() + if isAnimating { + isAnimating = false + DisplayLinkService.shared.remove(listner: self) + } + } +} + +extension AnimationController: DisplayLinkListner { + func update(delta: TimeInterval) { + guard isAnimating else { + DisplayLinkService.shared.remove(listner: self) + return + } + + currentTime += delta + if currentTime > animationDuration || animationDuration <= 0 { + start = end + current = end + isAnimating = false + animationDuration = 0.0 + currentTime = 0.0 +// updateClosure?(end) + completionClosure?() + refreshClosure?() + DisplayLinkService.shared.remove(listner: self) + } else { + let offset = timeFunction.profress(time: currentTime, duration: animationDuration) + current = AnimatableObject.valueBetween(start: start, end: end, offset: offset) +// updateClosure?(current) + refreshClosure?() + } + } +} + +extension ClosedRange: Animatable where Bound: BinaryFloatingPoint { + static func valueBetween(start: ClosedRange, end: ClosedRange, offset: Double) -> ClosedRange { + let castedOffset = Bound(offset) + return ClosedRange(uncheckedBounds: (lower: start.lowerBound + (end.lowerBound - start.lowerBound) * castedOffset, + upper: start.upperBound + (end.upperBound - start.upperBound) * castedOffset)) + } +} + +extension CGFloat: Animatable { + static func valueBetween(start: CGFloat, end: CGFloat, offset: Double) -> CGFloat { + return start + (end - start) * CGFloat(offset) + } +} + +extension Double: Animatable { + static func valueBetween(start: Double, end: Double, offset: Double) -> Double { + return start + (end - start) * Double(offset) + } +} + +extension Int: Animatable { + static func valueBetween(start: Int, end: Int, offset: Double) -> Int { + return start + Int(Double(end - start) * offset) + } +} + +extension CGPoint: Animatable { + static func valueBetween(start: CGPoint, end: CGPoint, offset: Double) -> CGPoint { + return CGPoint(x: start.x + (end.x - start.x) * CGFloat(offset), + y: start.y + (end.y - start.y) * CGFloat(offset)) + } +} + +extension CGRect: Animatable { + static func valueBetween(start: CGRect, end: CGRect, offset: Double) -> CGRect { + return CGRect(x: start.origin.x + (end.origin.x - start.origin.x) * CGFloat(offset), + y: start.origin.y + (end.origin.y - start.origin.y) * CGFloat(offset), + width: start.width + (end.width - start.width) * CGFloat(offset), + height: start.height + (end.height - start.height) * CGFloat(offset)) + } +} + +struct NSColorContainer: Animatable { + var color: GColor + + static func valueBetween(start: NSColorContainer, end: NSColorContainer, offset: Double) -> NSColorContainer { + return NSColorContainer(color: GColor.valueBetween(start: start.color, end: end.color, offset: offset)) + } +} + +extension GColor { + static func valueBetween(start: GColor, end: GColor, offset: Double) -> GColor { + let offsetF = CGFloat(offset) + let startCIColor = makeCIColor(color: start) + let endCIColor = makeCIColor(color: end) + return GColor(red: startCIColor.red + (endCIColor.red - startCIColor.red) * offsetF, + green: startCIColor.green + (endCIColor.green - startCIColor.green) * offsetF, + blue: startCIColor.blue + (endCIColor.blue - startCIColor.blue) * offsetF, + alpha: startCIColor.alpha + (endCIColor.alpha - startCIColor.alpha) * offsetF) + } +} diff --git a/submodules/GraphCore/src/Helpers/Array+Utils.swift b/submodules/GraphCore/src/Helpers/Array+Utils.swift new file mode 100644 index 0000000000..fc54119917 --- /dev/null +++ b/submodules/GraphCore/src/Helpers/Array+Utils.swift @@ -0,0 +1,23 @@ +// +// Array+Utils.swift +// GraphTest +// +// Created by Andrei Salavei on 4/7/19. +// Copyright © 2019 Andrei Salavei. All rights reserved. +// + +import Foundation +#if os(macOS) +import Cocoa +#else +import UIKit +#endif + +extension Array { + func safeElement(at index: Int) -> Element? { + if index >= 0 && index < count { + return self[index] + } + return nil + } +} diff --git a/submodules/GraphCore/src/Helpers/CGFloat.swift b/submodules/GraphCore/src/Helpers/CGFloat.swift new file mode 100644 index 0000000000..a36b0ca711 --- /dev/null +++ b/submodules/GraphCore/src/Helpers/CGFloat.swift @@ -0,0 +1,21 @@ +// +// CGFloat.swift +// GraphTest +// +// Created by Andrei Salavei on 4/11/19. +// Copyright © 2019 Andrei Salavei. All rights reserved. +// + +import Foundation +#if os(macOS) +import Cocoa +#else +import UIKit +#endif + + +extension CGFloat { + func roundedUpToPixelGrid() -> CGFloat { + return (self * deviceScale).rounded(.up) / deviceScale + } +} diff --git a/submodules/GraphCore/src/Helpers/CGPoint+Extensions.swift b/submodules/GraphCore/src/Helpers/CGPoint+Extensions.swift new file mode 100644 index 0000000000..09a34da9e8 --- /dev/null +++ b/submodules/GraphCore/src/Helpers/CGPoint+Extensions.swift @@ -0,0 +1,224 @@ +// +// CGPoint+Extensions.swift +// GraphTest +// +// Created by Andrei Salavei on 4/11/19. +// Copyright © 2019 Andrei Salavei. All rights reserved. +// + +import Foundation +#if os(macOS) +import Cocoa +#else +import UIKit +#endif + +extension CGPoint { + public init(vector: CGVector) { + self.init(x: vector.dx, y: vector.dy) + } + + + public init(angle: CGFloat) { + self.init(x: cos(angle), y: sin(angle)) + } + + + public mutating func offset(dx: CGFloat, dy: CGFloat) -> CGPoint { + x += dx + y += dy + return self + } + + public func length() -> CGFloat { + return sqrt(x*x + y*y) + } + + public func lengthSquared() -> CGFloat { + return x*x + y*y + } + + func normalized() -> CGPoint { + let len = length() + return len>0 ? self / len : CGPoint.zero + } + + public mutating func normalize() -> CGPoint { + self = normalized() + return self + } + + public func distanceTo(_ point: CGPoint) -> CGFloat { + return (self - point).length() + } + + public var angle: CGFloat { + return atan2(y, x) + } + + public var cgSize: CGSize { + return CGSize(width: x, height: y) + } + + func rotate(origin: CGPoint, angle: CGFloat) -> CGPoint { + let point = self - origin + let s = sin(angle) + let c = cos(angle) + return CGPoint(x: c * point.x - s * point.y, + y: s * point.x + c * point.y) + origin + } +} + +extension CGSize { + public var cgPoint: CGPoint { + return CGPoint(x: width, y: height) + } + + public init(point: CGPoint) { + self.init(width: point.x, height: point.y) + } +} + +public func + (left: CGPoint, right: CGPoint) -> CGPoint { + return CGPoint(x: left.x + right.x, y: left.y + right.y) +} + +public func += (left: inout CGPoint, right: CGPoint) { + left = left + right +} + +public func + (left: CGPoint, right: CGVector) -> CGPoint { + return CGPoint(x: left.x + right.dx, y: left.y + right.dy) +} + +public func += (left: inout CGPoint, right: CGVector) { + left = left + right +} + +public func - (left: CGPoint, right: CGPoint) -> CGPoint { return CGPoint(x: left.x - right.x, y: left.y - right.y) } +public func - (left: CGSize, right: CGSize) -> CGSize { return CGSize(width: left.width - right.width, height: left.height - right.height) } +public func - (left: CGSize, right: CGPoint) -> CGSize { return CGSize(width: left.width - right.x, height: left.height - right.x) } +public func - (left: CGPoint, right: CGSize) -> CGPoint { return CGPoint(x: left.x - right.width, y: left.y - right.height) } + +public func -= (left: inout CGPoint, right: CGPoint) { + left = left - right +} + +public func - (left: CGPoint, right: CGVector) -> CGPoint { + return CGPoint(x: left.x - right.dx, y: left.y - right.dy) +} + +public func -= (left: inout CGPoint, right: CGVector) { + left = left - right +} + +public func *= (left: inout CGPoint, right: CGPoint) { + left = left * right +} + +public func * (point: CGPoint, scalar: CGFloat) -> CGPoint { return CGPoint(x: point.x * scalar, y: point.y * scalar) } +public func * (point: CGSize, scalar: CGFloat) -> CGSize { return CGSize(width: point.width * scalar, height: point.height * scalar) } + +public func *= (point: inout CGPoint, scalar: CGFloat) { point = point * scalar } + +public func * (left: CGPoint, right: CGVector) -> CGPoint { + return CGPoint(x: left.x * right.dx, y: left.y * right.dy) +} + +public func *= (left: inout CGPoint, right: CGVector) { + left = left * right +} + +public func / (left: CGPoint, right: CGPoint) -> CGPoint { return CGPoint(x: left.x / right.x, y: left.y / right.y) } +public func / (left: CGSize, right: CGSize) -> CGSize { return CGSize(width: left.width / right.width, height: left.height / right.height) } +public func / (left: CGPoint, right: CGSize) -> CGPoint { return CGPoint(x: left.x / right.width, y: left.y / right.height) } +public func / (left: CGSize, right: CGPoint) -> CGSize { return CGSize(width: left.width / right.x, height: left.height / right.y) } +public func /= (left: inout CGPoint, right: CGPoint) { left = left / right } +public func /= (left: inout CGSize, right: CGSize) { left = left / right } +public func /= (left: inout CGSize, right: CGPoint) { left = left / right } +public func /= (left: inout CGPoint, right: CGSize) { left = left / right } + + +public func / (point: CGPoint, scalar: CGFloat) -> CGPoint { return CGPoint(x: point.x / scalar, y: point.y / scalar) } +public func / (point: CGSize, scalar: CGFloat) -> CGSize { return CGSize(width: point.width / scalar, height: point.height / scalar) } + +public func /= (point: inout CGPoint, scalar: CGFloat) { + point = point / scalar +} + +public func / (left: CGPoint, right: CGVector) -> CGPoint { + return CGPoint(x: left.x / right.dx, y: left.y / right.dy) +} + +public func / (left: CGSize, right: CGVector) -> CGSize { + return CGSize(width: left.width / right.dx, height: left.height / right.dy) +} + +public func /= (left: inout CGPoint, right: CGVector) { + left = left / right +} + +public func * (left: CGPoint, right: CGPoint) -> CGPoint { return CGPoint(x: left.x * right.x, y: left.y * right.y) } +public func * (left: CGPoint, right: CGSize) -> CGPoint { return CGPoint(x: left.x * right.width, y: left.y * right.height) } +public func *= (left: inout CGPoint, right: CGSize) { left = left * right } +public func * (left: CGSize, right: CGSize) -> CGSize { return CGSize(width: left.width * right.width, height: left.height * right.height) } +public func *= (left: inout CGSize, right: CGSize) { left = left * right } +public func * (left: CGSize, right: CGPoint) -> CGSize { return CGSize(width: left.width * right.x, height: left.height * right.y) } +public func *= (left: inout CGSize, right: CGPoint) { left = left * right } + + +public func lerp(start: CGPoint, end: CGPoint, t: CGFloat) -> CGPoint { + return start + (end - start) * t +} + +public func abs(_ point: CGPoint) -> CGPoint { + return CGPoint(x: abs(point.x), y: abs(point.y)) +} + +extension CGSize { + var isValid: Bool { + return width > 0 && height > 0 && width != .infinity && height != .infinity && width != .nan && height != .nan + } + + var ratio: CGFloat { + return width / height + } +} + + +extension CGRect { + static var identity: CGRect { + return CGRect(x: 0, y: 0, width: 1, height: 1) + } + + var center: CGPoint { + return origin + size.cgPoint / 2 + } + + var rounded: CGRect { + return CGRect(x: origin.x.rounded(), + y: origin.y.rounded(), + width: width.rounded(.up), + height: height.rounded(.up)) + } + + var mirroredVertically: CGRect { + return CGRect(x: origin.x, + y: 1.0 - (origin.y + height), + width: width, + height: height) + } +} + +extension CGAffineTransform { + func inverted(with size: CGSize) -> CGAffineTransform { + var transform = self + let transformedSize = CGRect(origin: .zero, size: size).applying(transform).size + transform.tx /= transformedSize.width; + transform.ty /= transformedSize.height; + transform = transform.inverted() + transform.tx *= transformedSize.width; + transform.ty *= transformedSize.height; + return transform + } +} diff --git a/submodules/GraphCore/src/Helpers/ClosedRange+Utils.swift b/submodules/GraphCore/src/Helpers/ClosedRange+Utils.swift new file mode 100644 index 0000000000..91ffc99d92 --- /dev/null +++ b/submodules/GraphCore/src/Helpers/ClosedRange+Utils.swift @@ -0,0 +1,20 @@ +// +// ClosedRange+Utils.swift +// GraphTest +// +// Created by Andrei Salavei on 3/11/19. +// Copyright © 2019 Andrei Salavei. All rights reserved. +// + +import Foundation +#if os(macOS) +import Cocoa +#else +import UIKit +#endif + +extension ClosedRange where Bound: Numeric { + var distance: Bound { + return upperBound - lowerBound + } +} diff --git a/submodules/GraphCore/src/Helpers/DisplayLinkService.swift b/submodules/GraphCore/src/Helpers/DisplayLinkService.swift new file mode 100644 index 0000000000..89f98b23b2 --- /dev/null +++ b/submodules/GraphCore/src/Helpers/DisplayLinkService.swift @@ -0,0 +1,119 @@ +// +// DisplayLinkService.swift +// GraphTest +// +// Created by Andrei Salavei on 4/7/19. +// Copyright © 2019 Andrei Salavei. All rights reserved. +// + +import Foundation +#if os(macOS) +import Cocoa +#else +import UIKit +#endif +import CoreGraphics + +public protocol DisplayLinkListner: class { + func update(delta: TimeInterval) +} + +// DispatchSource mares refreshes more accurate +class DisplayLinkService { + let listners = NSHashTable.weakObjects() + static let shared = DisplayLinkService() + + public func add(listner: DisplayLinkListner) { + listners.add(listner) + startDisplayLink() + } + + public func remove(listner: DisplayLinkListner) { + listners.remove(listner) + + if listners.count == 0 { + stopDisplayLink() + } + } + +// private init() { +// displayLink.add(to: .main, forMode: .common) +// displayLink.preferredFramesPerSecond = 60 +// displayLink.isPaused = true +// } +// +// // MARK: - Display Link +// private lazy var displayLink: CADisplayLink! = { CADisplayLink(target: self, selector: #selector(displayLinkDidFire)) } () +// private var previousTickTime = 0.0 +// +// private func startDisplayLink() { +// guard displayLink.isPaused else { +// return +// } +// previousTickTime = CACurrentMediaTime() +// displayLink.isPaused = false +// } +// +// @objc private func displayLinkDidFire(_ displayLink: CADisplayLink) { +// let currentTime = CACurrentMediaTime() +// let delta = currentTime - previousTickTime +// previousTickTime = currentTime +// let allListners = listners.allObjects +// var hasListners = false +// for listner in allListners { +// (listner as! DisplayLinkListner).update(delta: delta) +// hasListners = true +// } +// +// if !hasListners { +// stopDisplayLink() +// } +// } +// +// private func stopDisplayLink() { +// displayLink.isPaused = true +// } + + private init() { + dispatchSourceTimer.schedule(deadline: .now() + 1.0 / 60, repeating: 1.0 / 60) + dispatchSourceTimer.setEventHandler { + DispatchQueue.main.sync { + self.fire() + } + } + } + + private var dispatchSourceTimer = DispatchSource.makeTimerSource(flags: [], queue: .global(qos: .userInteractive)) + private var dispatchSourceTimerStarted: Bool = false + private var previousTickTime = 0.0 + + private func startDisplayLink() { + guard !dispatchSourceTimerStarted else { return } + dispatchSourceTimerStarted = true + previousTickTime = CACurrentMediaTime() + dispatchSourceTimer.resume() + } + + private func stopDisplayLink() { + guard dispatchSourceTimerStarted else { return } + dispatchSourceTimerStarted = false + dispatchSourceTimer.suspend() + } + + public func fire() { + let currentTime = CACurrentMediaTime() + + let delta = currentTime - previousTickTime + previousTickTime = currentTime + let allListners = listners.allObjects + var hasListners = false + for listner in allListners { + (listner as! DisplayLinkListner).update(delta: delta) + hasListners = true + } + + if !hasListners { + stopDisplayLink() + } + } +} diff --git a/submodules/GraphCore/src/Helpers/GlobalHelpers.swift b/submodules/GraphCore/src/Helpers/GlobalHelpers.swift new file mode 100644 index 0000000000..3f5c488ff9 --- /dev/null +++ b/submodules/GraphCore/src/Helpers/GlobalHelpers.swift @@ -0,0 +1,12 @@ +// +// GlobalHelpers.swift +// TrackingRecorder +// +// Created by Andrew Solovey on 07.09.2018. +// Copyright © 2018 Andrew Solovey. All rights reserved. +// + +public func crop(_ lower: Type, _ val: Type, _ upper: Type) -> Type where Type : Comparable { + assert(lower < upper, "Invalid lover and upper values") + return max(lower, min(upper, val)) +} diff --git a/submodules/GraphCore/src/Helpers/NumberFormatter+Utils.swift b/submodules/GraphCore/src/Helpers/NumberFormatter+Utils.swift new file mode 100644 index 0000000000..7f57b80484 --- /dev/null +++ b/submodules/GraphCore/src/Helpers/NumberFormatter+Utils.swift @@ -0,0 +1,24 @@ +// +// NumberFormatter+Utils.swift +// GraphTest +// +// Created by Andrei Salavei on 4/12/19. +// Copyright © 2019 Andrei Salavei. All rights reserved. +// + +import Foundation +#if os(macOS) +import Cocoa +#else +import UIKit +#endif + +extension NumberFormatter { + func string(from value: CGFloat) -> String { + return string(from: Double(value)) + } + + func string(from value: Double) -> String { + return string(from: NSNumber(value: Double(value))) ?? "" + } +} diff --git a/submodules/GraphCore/src/Helpers/ScalesNumberFormatter.swift b/submodules/GraphCore/src/Helpers/ScalesNumberFormatter.swift new file mode 100644 index 0000000000..9fd144249d --- /dev/null +++ b/submodules/GraphCore/src/Helpers/ScalesNumberFormatter.swift @@ -0,0 +1,37 @@ +// +// ScalesNumberFormatter.swift +// GraphTest +// +// Created by Andrei Salavei on 4/13/19. +// Copyright © 2019 Andrei Salavei. All rights reserved. +// + +import Foundation +#if os(macOS) +import Cocoa +#else +import UIKit +#endif + +private let milionsScale = "M" +private let thousandsScale = "K" + +class ScalesNumberFormatter: NumberFormatter { + override func string(from number: NSNumber) -> String? { + let value = number.doubleValue + let pow = log10(value) + if pow >= 6 { + guard let string = super.string(from: NSNumber(value: value / 1_000_000)) else { + return nil + } + return string + milionsScale + } else if pow >= 4 { + guard let string = super.string(from: NSNumber(value: value / 1_000)) else { + return nil + } + return string + thousandsScale + } else { + return super.string(from: number) + } + } +} diff --git a/submodules/GraphCore/src/Helpers/TextUtils.swift b/submodules/GraphCore/src/Helpers/TextUtils.swift new file mode 100644 index 0000000000..b9a1c2e093 --- /dev/null +++ b/submodules/GraphCore/src/Helpers/TextUtils.swift @@ -0,0 +1,192 @@ +// +// TextUtils.swift +// GraphCore +// +// Created by Mikhail Filimonov on 26.02.2020. +// Copyright © 2020 Telegram. All rights reserved. +// + +import Foundation +#if os(macOS) +import Cocoa +#else +import UIKit +#endif + +#if os(iOS) +typealias NSFont = UIFont +#endif + +private let defaultFont:NSFont = NSFont.systemFont(ofSize: 14) + +extension NSAttributedString { + var size: CGSize { + return textSize(with: self.string, font: self.attribute(.font, at: 0, effectiveRange: nil) as? NSFont ?? defaultFont) + } +} + +func textSize(with string: String, font: NSFont) -> CGSize { + let attributedString:NSAttributedString = NSAttributedString(string: string, attributes: [.font : font]) + let layout = LabelNode.layoutText(attributedString, CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude)) + var size:CGSize = layout.0.size + size.width = ceil(size.width) + size.height = ceil(size.height) + return size +} + + + +private final class LabelNodeLine { + let line: CTLine + let frame: CGRect + + init(line: CTLine, frame: CGRect) { + self.line = line + self.frame = frame + } +} + + +public final class LabelNodeLayout: NSObject { + fileprivate let attributedString: NSAttributedString? + fileprivate let truncationType: CTLineTruncationType + fileprivate let constrainedSize: CGSize + fileprivate let lines: [LabelNodeLine] + + let size: CGSize + + fileprivate init(attributedString: NSAttributedString?, truncationType: CTLineTruncationType, constrainedSize: CGSize, size: CGSize, lines: [LabelNodeLine]) { + self.attributedString = attributedString + self.truncationType = truncationType + self.constrainedSize = constrainedSize + self.size = size + self.lines = lines + } + + var numberOfLines: Int { + return self.lines.count + } + + var trailingLineWidth: CGFloat { + if let lastLine = self.lines.last { + return lastLine.frame.width + } else { + return 0.0 + } + } +} + +class LabelNode: NSObject { + private var currentLayout: LabelNodeLayout? + + private class func getlayout(attributedString: NSAttributedString?, truncationType: CTLineTruncationType, constrainedSize: CGSize) -> LabelNodeLayout { + + if let attributedString = attributedString { + let font: CTFont + if attributedString.length != 0 { + if let stringFont = attributedString.attribute(NSAttributedString.Key(kCTFontAttributeName as String), at: 0, effectiveRange: nil) { + font = stringFont as! CTFont + } else if let f = attributedString.attribute(.font, at: 0, effectiveRange: nil) as? NSFont { + font = f + } else { + font = defaultFont + } + } else { + font = defaultFont + } + + let fontAscent = CTFontGetAscent(font) + let fontDescent = CTFontGetDescent(font) + let fontLineHeight = floor(fontAscent + fontDescent) + let fontLineSpacing = floor(fontLineHeight * 0.12) + + var lines: [LabelNodeLine] = [] + + + + var maybeTypesetter: CTTypesetter? + maybeTypesetter = CTTypesetterCreateWithAttributedString(attributedString as CFAttributedString) + if maybeTypesetter == nil { + return LabelNodeLayout(attributedString: attributedString, truncationType: truncationType, constrainedSize: constrainedSize, size: CGSize(), lines: []) + } + + let typesetter = maybeTypesetter! + var layoutSize = CGSize() + + let lineOriginY = floor(layoutSize.height + fontLineHeight - fontLineSpacing * 2.0) + + let lastLineCharacterIndex: CFIndex = 0 + + let coreTextLine: CTLine + + let originalLine = CTTypesetterCreateLineWithOffset(typesetter, CFRange(location: lastLineCharacterIndex, length: attributedString.length - lastLineCharacterIndex), 0.0) + + if CTLineGetTypographicBounds(originalLine, nil, nil, nil) - CTLineGetTrailingWhitespaceWidth(originalLine) < Double(constrainedSize.width) { + coreTextLine = originalLine + } else { + var truncationTokenAttributes: [NSAttributedString.Key : Any] = [:] + truncationTokenAttributes[NSAttributedString.Key(kCTFontAttributeName as String)] = font + truncationTokenAttributes[NSAttributedString.Key(kCTForegroundColorFromContextAttributeName as String)] = true as NSNumber + let tokenString = "\u{2026}" + let truncatedTokenString = NSAttributedString(string: tokenString, attributes: truncationTokenAttributes) + let truncationToken = CTLineCreateWithAttributedString(truncatedTokenString) + + coreTextLine = CTLineCreateTruncatedLine(originalLine, Double(constrainedSize.width), truncationType, truncationToken) ?? truncationToken + + } + + let lineWidth = ceil(CGFloat(CTLineGetTypographicBounds(coreTextLine, nil, nil, nil) - CTLineGetTrailingWhitespaceWidth(coreTextLine))) + let lineFrame = CGRect(x: 0, y: lineOriginY, width: lineWidth, height: fontLineHeight) + layoutSize.height += fontLineHeight + fontLineSpacing + layoutSize.width = max(layoutSize.width, lineWidth) + + lines.append(LabelNodeLine(line: coreTextLine, frame: lineFrame)) + + return LabelNodeLayout(attributedString: attributedString, truncationType: truncationType, constrainedSize: constrainedSize, size: CGSize(width: ceil(layoutSize.width), height: ceil(layoutSize.height)), lines: lines) + } else { + return LabelNodeLayout(attributedString: attributedString, truncationType: truncationType, constrainedSize: constrainedSize, size: CGSize(), lines: []) + } + } + + + func draw(_ dirtyRect: CGRect, in ctx: CGContext, backingScaleFactor: CGFloat) { + + ctx.saveGState() + + ctx.setAllowsFontSubpixelPositioning(true) + ctx.setShouldSubpixelPositionFonts(true) + + ctx.setAllowsAntialiasing(true) + ctx.setShouldAntialias(true) + + ctx.setAllowsFontSmoothing(backingScaleFactor == 1.0) + ctx.setShouldSmoothFonts(backingScaleFactor == 1.0) + + let context:CGContext = ctx + + if let layout = self.currentLayout { + let textMatrix = context.textMatrix + let textPosition = context.textPosition + context.textMatrix = CGAffineTransform(scaleX: 1.0, y: -1.0) + for i in 0 ..< layout.lines.count { + let line = layout.lines[i] + context.textPosition = CGPoint(x: dirtyRect.minX, y: line.frame.origin.y + dirtyRect.minY) + CTLineDraw(line.line, context) + } + + context.textMatrix = textMatrix + context.textPosition = CGPoint(x: textPosition.x, y: textPosition.y) + } + ctx.restoreGState() + } + + + + class func layoutText(_ attributedString: NSAttributedString?, _ constrainedSize: CGSize, _ truncationType: CTLineTruncationType = .end) -> (LabelNodeLayout, LabelNode) { + let layout: LabelNodeLayout + layout = LabelNode.getlayout(attributedString: attributedString, truncationType: truncationType, constrainedSize: constrainedSize) + let node = LabelNode() + node.currentLayout = layout + return (layout, node) + } +} diff --git a/submodules/GraphCore/src/Helpers/TimeInterval+Utils.swift b/submodules/GraphCore/src/Helpers/TimeInterval+Utils.swift new file mode 100644 index 0000000000..74343d1225 --- /dev/null +++ b/submodules/GraphCore/src/Helpers/TimeInterval+Utils.swift @@ -0,0 +1,32 @@ +// +// TimeInterval+Utils.swift +// GraphTest +// +// Created by Andrei Salavei on 3/13/19. +// Copyright © 2019 Andrei Salavei. All rights reserved. +// + +import Foundation +#if os(macOS) +import Cocoa +#else +import UIKit +#endif + +extension TimeInterval { + static let minute: TimeInterval = 60 + static let hour: TimeInterval = 60 * 60 + static let day: TimeInterval = 60 * 60 * 24 + static let osXDuration: TimeInterval = 0.25 + static let expandAnimationDuration: TimeInterval = 0.4 + static var animationDurationMultipler: Double = 1.0 + + static var defaultDuration: TimeInterval { + return innerDefaultDuration * animationDurationMultipler + } + private static var innerDefaultDuration: TimeInterval = osXDuration + + static func setDefaultSuration(_ duration: TimeInterval) { + innerDefaultDuration = duration + } +} diff --git a/submodules/GraphCore/src/Helpers/TimeZone.swift b/submodules/GraphCore/src/Helpers/TimeZone.swift new file mode 100644 index 0000000000..457ef9aec1 --- /dev/null +++ b/submodules/GraphCore/src/Helpers/TimeZone.swift @@ -0,0 +1,41 @@ +// +// TimeZone.swift +// GraphTest +// +// Created by Andrei Salavei on 4/9/19. +// Copyright © 2019 Andrei Salavei. All rights reserved. +// + +import Foundation +#if os(macOS) +import Cocoa +#else +import UIKit +#endif + +extension TimeZone { + static let utc = TimeZone(secondsFromGMT: 0)! +} + +extension Locale { + static let posix = Locale(identifier: "en_US_POSIX") +} + +extension Calendar { + static let utc: Calendar = { + var calendar = Calendar.current + calendar.locale = Locale.posix + calendar.timeZone = TimeZone.utc + return calendar + }() +} + +extension DateFormatter { + static func utc(format: String = "") -> DateFormatter { + let formatter = DateFormatter() + formatter.calendar = Calendar.utc + formatter.dateFormat = format + formatter.timeZone = TimeZone.utc + return formatter + } +} diff --git a/submodules/GraphCore/src/Helpers/UIColor+Utils.swift b/submodules/GraphCore/src/Helpers/UIColor+Utils.swift new file mode 100644 index 0000000000..fc74ce68a3 --- /dev/null +++ b/submodules/GraphCore/src/Helpers/UIColor+Utils.swift @@ -0,0 +1,68 @@ +// +// GColor+Utils.swift +// GraphTest +// +// Created by Andrei Salavei on 3/11/19. +// Copyright © 2019 Andrei Salavei. All rights reserved. +// + +import Foundation +#if os(macOS) +import Cocoa +#else +import UIKit +#endif + +func makeCIColor(color: GColor) -> CIColor { + #if os(macOS) + return CIColor(color: color)! + #else + return CIColor(color: color) + #endif +} + +extension GColor { + var redValue: CGFloat{ return makeCIColor(color: self).red } + var greenValue: CGFloat{ return makeCIColor(color: self).green } + var blueValue: CGFloat{ return makeCIColor(color: self).blue } + var alphaValue: CGFloat{ return makeCIColor(color: self).alpha } + + convenience init?(hexString: String) { + let r, g, b, a: CGFloat + + if hexString.hasPrefix("#") { + let start = hexString.index(hexString.startIndex, offsetBy: 1) + let hexColor = String(hexString[start...]) + + if hexColor.count == 8 { + let scanner = Scanner(string: hexColor) + var hexNumber: UInt64 = 0 + + if scanner.scanHexInt64(&hexNumber) { + r = CGFloat((hexNumber & 0xff000000) >> 24) / 255 + g = CGFloat((hexNumber & 0x00ff0000) >> 16) / 255 + b = CGFloat((hexNumber & 0x0000ff00) >> 8) / 255 + a = CGFloat(hexNumber & 0x000000ff) / 255 + + self.init(red: r, green: g, blue: b, alpha: a) + return + } + } else if hexColor.count == 6 { + let scanner = Scanner(string: hexColor) + var hexNumber: UInt64 = 0 + + if scanner.scanHexInt64(&hexNumber) { + r = CGFloat((hexNumber & 0xff0000) >> 16) / 255 + g = CGFloat((hexNumber & 0x00ff00) >> 8) / 255 + b = CGFloat((hexNumber & 0x0000ff) >> 0) / 255 + + self.init(red: r, green: g, blue: b, alpha: 1.0) + return + } + } + } + return nil + } + + +} diff --git a/submodules/GraphCore/src/Helpers/UIImage+Utils.swift b/submodules/GraphCore/src/Helpers/UIImage+Utils.swift new file mode 100644 index 0000000000..76390fe4da --- /dev/null +++ b/submodules/GraphCore/src/Helpers/UIImage+Utils.swift @@ -0,0 +1,91 @@ +// +// GImage+Utils.swift +// GraphTest +// +// Created by Andrei Salavei on 4/8/19. +// Copyright © 2019 Andrei Salavei. All rights reserved. +// + +import Foundation +#if os(macOS) +import Cocoa +#else +import UIKit +#endif + +#if os(iOS) +public typealias GImage = UIImage +#else +public typealias GImage = NSImage +#endif + +#if os(macOS) +internal let deviceColorSpace: CGColorSpace = { + if #available(OSX 10.11.2, *) { + if let colorSpace = CGColorSpace(name: CGColorSpace.displayP3) { + return colorSpace + } else { + return CGColorSpaceCreateDeviceRGB() + } + } else { + return CGColorSpaceCreateDeviceRGB() + } +}() +#else +internal let deviceColorSpace: CGColorSpace = { + if #available(iOSApplicationExtension 9.3, iOS 9.3, *) { + if let colorSpace = CGColorSpace(name: CGColorSpace.displayP3) { + return colorSpace + } else { + return CGColorSpaceCreateDeviceRGB() + } + } else { + return CGColorSpaceCreateDeviceRGB() + } +}() +#endif + +var deviceScale: CGFloat { + #if os(macOS) + return NSScreen.main?.backingScaleFactor ?? 1.0 + #else + return UIScreen.main.scale + #endif +} + +func generateImage(_ size: CGSize, contextGenerator: (CGSize, CGContext) -> Void, opaque: Bool = false, scale: CGFloat? = nil) -> GImage? { + let selectedScale = scale ?? deviceScale + let scaledSize = CGSize(width: size.width * selectedScale, height: size.height * selectedScale) + let bytesPerRow = (4 * Int(scaledSize.width) + 15) & (~15) + let length = bytesPerRow * Int(scaledSize.height) + let bytes = malloc(length)!.assumingMemoryBound(to: Int8.self) + + guard let provider = CGDataProvider(dataInfo: bytes, data: bytes, size: length, releaseData: { bytes, _, _ in + free(bytes) + }) + else { + return nil + } + + let bitmapInfo = CGBitmapInfo(rawValue: CGBitmapInfo.byteOrder32Little.rawValue | (opaque ? CGImageAlphaInfo.noneSkipFirst.rawValue : CGImageAlphaInfo.premultipliedFirst.rawValue)) + + guard let context = CGContext(data: bytes, width: Int(scaledSize.width), height: Int(scaledSize.height), bitsPerComponent: 8, bytesPerRow: bytesPerRow, space: deviceColorSpace, bitmapInfo: bitmapInfo.rawValue) else { + return nil + } + + context.scaleBy(x: selectedScale, y: selectedScale) + + contextGenerator(size, context) + + guard let image = CGImage(width: Int(scaledSize.width), height: Int(scaledSize.height), bitsPerComponent: 8, bitsPerPixel: 32, bytesPerRow: bytesPerRow, space: deviceColorSpace, bitmapInfo: bitmapInfo, provider: provider, decode: nil, shouldInterpolate: false, intent: .defaultIntent) + else { + return nil + } + #if os(macOS) + return GImage(cgImage: image, size: size) + #else + return GImage(cgImage: image, scale: selectedScale, orientation: .up) + #endif +} + + diff --git a/submodules/GraphCore/src/Helpers/UIView+Extensions.swift b/submodules/GraphCore/src/Helpers/UIView+Extensions.swift new file mode 100644 index 0000000000..ef76b00fcb --- /dev/null +++ b/submodules/GraphCore/src/Helpers/UIView+Extensions.swift @@ -0,0 +1,25 @@ +// +// GView+Extensions.swift +// GraphTest +// +// Created by Andrei Salavei on 4/10/19. +// Copyright © 2019 Andrei Salavei. All rights reserved. +// + +import Foundation +#if os(macOS) +import Cocoa +#else +import UIKit +#endif + +#if os(macOS) +public typealias GView = NSView +#else +public typealias GView = UIView +#endif + + +extension GView { + static let oneDevicePixel: CGFloat = (1.0 / max(2, min(1, deviceScale))) +} diff --git a/submodules/GraphCore/src/Models/ChartLineData.swift b/submodules/GraphCore/src/Models/ChartLineData.swift new file mode 100644 index 0000000000..faf7929924 --- /dev/null +++ b/submodules/GraphCore/src/Models/ChartLineData.swift @@ -0,0 +1,81 @@ +// +// ChartLineData.swift +// GraphTest +// +// Created by Andrei Salavei on 3/13/19. +// Copyright © 2019 Andrei Salavei. All rights reserved. +// + +import Foundation +#if os(macOS) +import Cocoa +#else +import UIKit +#endif + +struct ChartLineData { + var title: String + var color: GColor + var width: CGFloat? + var points: [CGPoint] +} + +extension ChartLineData { + static func horizontalRange(lines: [ChartLineData]) -> ClosedRange? { + guard let firstPoint = lines.first?.points.first else { return nil } + var hMin: CGFloat = firstPoint.x + var hMax: CGFloat = firstPoint.x + + for line in lines { + if let first = line.points.first, + let last = line.points.last { + hMin = min(hMin, first.x) + hMax = max(hMax, last.x) + } + } + + return hMin...hMax + } + + static func verticalRange(lines: [ChartLineData], calculatingRange: ClosedRange? = nil, addBounds: Bool = false) -> ClosedRange? { + if let calculatingRange = calculatingRange { + guard let initalStart = lines.first?.points.first(where: { $0.x > calculatingRange.lowerBound && + $0.x < calculatingRange.upperBound }) else { return nil } + var vMin: CGFloat = initalStart.y + var vMax: CGFloat = initalStart.y + for line in lines { + if var index = line.points.firstIndex(where: { $0.x > calculatingRange.lowerBound }) { + if addBounds { + index = max(0, index - 1) + } + while index < line.points.count { + let point = line.points[index] + if point.x < calculatingRange.upperBound { + vMin = min(vMin, point.y) + vMax = max(vMax, point.y) + } else if addBounds { + vMin = min(vMin, point.y) + vMax = max(vMax, point.y) + break + } else { + break + } + index += 1 + } + } + } + return vMin...vMax + } else { + guard let firstPoint = lines.first?.points.first else { return nil } + var vMin: CGFloat = firstPoint.y + var vMax: CGFloat = firstPoint.y + for line in lines { + for point in line.points { + vMin = min(vMin, point.y) + vMax = max(vMax, point.y) + } + } + return vMin...vMax + } + } +} diff --git a/submodules/GraphCore/src/Models/ColorMode.swift b/submodules/GraphCore/src/Models/ColorMode.swift new file mode 100644 index 0000000000..f58d3003bb --- /dev/null +++ b/submodules/GraphCore/src/Models/ColorMode.swift @@ -0,0 +1,195 @@ +// +// colorMode.swift +// GraphTest +// +// Created by Andrew Solovey on 15/03/2019. +// Copyright © 2019 Andrei Salavei. All rights reserved. +// + +import Foundation +#if os(macOS) +import Cocoa +#else +import UIKit +#endif + +#if os(iOS) +public typealias GColor = UIColor +#else +public typealias GColor = NSColor +#endif + +#if os(iOS) +typealias NSEdgeInsets = UIEdgeInsets +#endif + +public protocol GColorModeContainer { + func apply(colorMode: GColorMode, animated: Bool) +} + +public enum GColorMode { + case day + case night +} + +extension GColorMode { + public var chartTitleColor: GColor { // Текст с датой на чарте + switch self { + case .day: return .black + case .night: return .white + } + } + + public var actionButtonColor: GColor { // Кнопка Zoom Out/ Смена режима день/ночь + switch self { + case .day: return GColor(red: 53/255.0, green: 120/255.0, blue: 246/255.0, alpha: 1.0) + case .night: return GColor(red: 84/255.0, green: 164/255.0, blue: 247/255.0, alpha: 1.0) + } + } + + public var tableBackgroundColor: GColor { + switch self { + case .day: return GColor(red: 239/255.0, green: 239/255.0, blue: 244/255.0, alpha: 1.0) + case .night: return GColor(red: 24/255.0, green: 34/255.0, blue: 45/255.0, alpha: 1.0) + } + } + + public var chartBackgroundColor: GColor { + switch self { + case .day: return GColor(red: 254/255.0, green: 254/255.0, blue: 254/255.0, alpha: 1.0) + case .night: return GColor(red: 34/255.0, green: 47/255.0, blue: 63/255.0, alpha: 1.0) + } + } + + public var sectionTitleColor: GColor { + switch self { + case .day: return GColor(red: 109/255.0, green: 109/255.0, blue: 114/255.0, alpha: 1.0) + case .night: return GColor(red: 133/255.0, green: 150/255.0, blue: 171/255.0, alpha: 1.0) + } + } + + public var tableSeparatorColor: GColor { + switch self { + case .day: return GColor(red: 200/255.0, green: 199/255.0, blue: 204/255.0, alpha: 1.0) + case .night: return GColor(red: 18/255.0, green: 26/255.0, blue: 35/255.0, alpha: 1.0) + } + } + + public var chartLabelsColor: GColor { + switch self { + case .day: return GColor(red: 37/255.0, green: 37/255.0, blue: 41/255.0, alpha: 0.5) + case .night: return GColor(red: 186/255.0, green: 204/255.0, blue: 225/255.0, alpha: 0.6) + } + } + + public var chartHelperLinesColor: GColor { + switch self { + case .day: return GColor(red: 24/255.0, green: 45/255.0, blue: 59/255.0, alpha: 0.1) + case .night: return GColor(red: 133/255.0, green: 150/255.0, blue: 171/255.0, alpha: 0.20) + } + } + + public var chartStrongLinesColor: GColor { + switch self { + case .day: return GColor(red: 24/255.0, green: 45/255.0, blue: 59/255.0, alpha: 0.35) + case .night: return GColor(red: 186/255.0, green: 204/255.0, blue: 225/255.0, alpha: 0.45) + } + } + + public var barChartStrongLinesColor: GColor { + switch self { + case .day: return GColor(red: 37/255.0, green: 37/255.0, blue: 41/255.0, alpha: 0.2) + case .night: return GColor(red: 186/255.0, green: 204/255.0, blue: 225/255.0, alpha: 0.45) + } + } + + public var chartDetailsTextColor: GColor { + switch self { + case .day: return GColor(red: 109/255.0, green: 109/255.0, blue: 114/255.0, alpha: 1.0) + case .night: return GColor(red: 254/255.0, green: 254/255.0, blue: 254/255.0, alpha: 1.0) + } + } + + public var chartDetailsArrowColor: GColor { + switch self { + case .day: return GColor(red: 197/255.0, green: 199/255.0, blue: 205/255.0, alpha: 1.0) + case .night: return GColor(red: 76/255.0, green: 84/255.0, blue: 96/255.0, alpha: 1.0) + } + } + + public var chartDetailsViewColor: GColor { + switch self { + case .day: return GColor(red: 245/255.0, green: 245/255.0, blue: 251/255.0, alpha: 1.0) + case .night: return GColor(red: 25/255.0, green: 35/255.0, blue: 47/255.0, alpha: 1.0) + } + } + + public var descriptionChatNameColor: GColor { + switch self { + case .day: return .black + case .night: return GColor(red: 254/255.0, green: 254/255.0, blue: 254/255.0, alpha: 1.0) + } + } + + public var descriptionActionColor: GColor { + switch self { + case .day: return GColor(red: 1/255.0, green: 125/255.0, blue: 229/255.0, alpha: 1.0) + case .night: return GColor(red: 24/255.0, green: 145/255.0, blue: 255/255.0, alpha: 1.0) + } + } + + public var rangeViewBackgroundColor: GColor { + switch self { + case .day: return GColor(red: 254/255.0, green: 254/255.0, blue: 254/255.0, alpha: 1.0) + case .night: return GColor(red: 34/255.0, green: 47/255.0, blue: 63/255.0, alpha: 1.0) + } + } + + public var rangeViewFrameColor: GColor { + switch self { + case .day: return GColor(red: 202/255.0, green: 212/255.0, blue: 222/255.0, alpha: 1.0) + case .night: return GColor(red: 53/255.0, green: 70/255.0, blue: 89/255.0, alpha: 1.0) + } + } + + public var rangeViewTintColor: GColor { + switch self { + case .day: return GColor(red: 239/255.0, green: 239/255.0, blue: 244/255.0, alpha: 0.5) + case .night: return GColor(red: 24/255.0, green: 34/255.0, blue: 45/255.0, alpha: 0.5) + } + } + + public var rangeViewMarkerColor: GColor { + switch self { + case .day: return GColor.white + case .night: return GColor.white + } + } + + + public var viewTintColor: GColor { + switch self { + case .day: return .black + case .night: return GColor(red: 254/255.0, green: 254/255.0, blue: 254/255.0, alpha: 1.0) + } + } + + public var rangeCropImage: GImage? { + switch self { + case .day: + let image = GImage(named: "selection_frame_light") + #if os(macOS) + image?.resizingMode = .stretch + image?.capInsets = NSEdgeInsets(top: 15, left: 15, bottom: 15, right: 15) + #endif + return image + case .night: + let image = GImage(named: "selection_frame_dark") + #if os(macOS) + image?.resizingMode = .stretch + image?.capInsets = NSEdgeInsets(top: 15, left: 15, bottom: 15, right: 15) + #endif + return image + } + } +} diff --git a/submodules/GraphCore/src/Models/LinesChartLabel.swift b/submodules/GraphCore/src/Models/LinesChartLabel.swift new file mode 100644 index 0000000000..4f2ad665fa --- /dev/null +++ b/submodules/GraphCore/src/Models/LinesChartLabel.swift @@ -0,0 +1,30 @@ +// +// LinesChartLabel.swift +// GraphTest +// +// Created by Andrei Salavei on 3/18/19. +// Copyright © 2019 Andrei Salavei. All rights reserved. +// + +import Foundation +#if os(macOS) +import Cocoa +#else +import UIKit +#endif + +struct LinesChartLabel: Hashable { + let value: CGFloat + let text: String +} + +class AnimatedLinesChartLabels { + var labels: [LinesChartLabel] + var isAppearing: Bool = false + let alphaAnimator: AnimationController + + init(labels: [LinesChartLabel], alphaAnimator: AnimationController) { + self.labels = labels + self.alphaAnimator = alphaAnimator + } +} diff --git a/submodules/GraphCore/src/Models/LinesSelectionLabel.swift b/submodules/GraphCore/src/Models/LinesSelectionLabel.swift new file mode 100644 index 0000000000..3f7188fefd --- /dev/null +++ b/submodules/GraphCore/src/Models/LinesSelectionLabel.swift @@ -0,0 +1,20 @@ +// +// LinesSelectionLabel.swift +// GraphTest +// +// Created by Andrei Salavei on 3/18/19. +// Copyright © 2019 Andrei Salavei. All rights reserved. +// + +import Foundation +#if os(macOS) +import Cocoa +#else +import UIKit +#endif + +struct LinesSelectionLabel { + let coordinate: CGPoint + let valueText: String + let color: GColor +} diff --git a/submodules/HashtagSearchUI/Sources/HashtagSearchController.swift b/submodules/HashtagSearchUI/Sources/HashtagSearchController.swift index a6e14bb87e..f9675922f4 100644 --- a/submodules/HashtagSearchUI/Sources/HashtagSearchController.swift +++ b/submodules/HashtagSearchUI/Sources/HashtagSearchController.swift @@ -71,6 +71,7 @@ public final class HashtagSearchController: TelegramBaseController { }, toggleArchivedFolderHiddenByDefault: { }, activateChatPreview: { _, _, gesture in gesture?.cancel() + }, present: { _ in }) let previousSearchItems = Atomic<[ChatListSearchEntry]?>(value: nil) diff --git a/submodules/LegacyComponents/Sources/TGMediaPickerSendActionSheetController.m b/submodules/LegacyComponents/Sources/TGMediaPickerSendActionSheetController.m index 1471e0ac08..8a689e2ec1 100644 --- a/submodules/LegacyComponents/Sources/TGMediaPickerSendActionSheetController.m +++ b/submodules/LegacyComponents/Sources/TGMediaPickerSendActionSheetController.m @@ -146,20 +146,6 @@ } [self.view addSubview:_effectView]; - /* - let contextMenu = PresentationThemeContextMenu( - dimColor: UIColor(rgb: 0x000a26, alpha: 0.2), - backgroundColor: UIColor(rgb: 0xf9f9f9, alpha: 0.78), - itemSeparatorColor: UIColor(rgb: 0x3c3c43, alpha: 0.2), - sectionSeparatorColor: UIColor(rgb: 0x8a8a8a, alpha: 0.2), - itemBackgroundColor: UIColor(rgb: 0x000000, alpha: 0.0), - itemHighlightedBackgroundColor: UIColor(rgb: 0x3c3c43, alpha: 0.2), - primaryColor: UIColor(rgb: 0x000000, alpha: 1.0), - secondaryColor: UIColor(rgb: 0x000000, alpha: 0.8), - destructiveColor: UIColor(rgb: 0xff3b30) - ) - */ - _containerView = [[UIView alloc] init]; if (_isDark) { _containerView.backgroundColor = UIColorRGB(0x1f1f1f); diff --git a/submodules/PeerInfoUI/Sources/ChannelMembersController.swift b/submodules/PeerInfoUI/Sources/ChannelMembersController.swift index cb1c05e765..b6e92ee36f 100644 --- a/submodules/PeerInfoUI/Sources/ChannelMembersController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelMembersController.swift @@ -356,7 +356,7 @@ public func channelMembersController(context: AccountContext, peerId: PeerId) -> |> take(1) |> deliverOnMainQueue).start(next: { members in let disabledIds = members?.compactMap({$0.peer.id}) ?? [] - let contactsController = context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: context, mode: .peerSelection(searchChatList: false, searchGroups: false), options: [], filters: [.excludeSelf, .disable(disabledIds)])) + let contactsController = context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: context, mode: .peerSelection(searchChatList: false, searchGroups: false, searchChannels: false), options: [], filters: [.excludeSelf, .disable(disabledIds)])) addMembersDisposable.set((contactsController.result |> deliverOnMainQueue diff --git a/submodules/PeerInfoUI/Sources/GroupInfoController.swift b/submodules/PeerInfoUI/Sources/GroupInfoController.swift index 367096905c..c9213f94c9 100644 --- a/submodules/PeerInfoUI/Sources/GroupInfoController.swift +++ b/submodules/PeerInfoUI/Sources/GroupInfoController.swift @@ -1665,7 +1665,7 @@ public func groupInfoController(context: AccountContext, peerId originalPeerId: } })) } else { - contactsController = context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: context, mode: .peerSelection(searchChatList: false, searchGroups: false), options: options, filters: [.excludeSelf, .disable(recentIds)])) + contactsController = context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: context, mode: .peerSelection(searchChatList: false, searchGroups: false, searchChannels: false), options: options, filters: [.excludeSelf, .disable(recentIds)])) } confirmationImpl = { [weak contactsController] peerId in diff --git a/submodules/PhotoResources/Sources/PhotoResources.swift b/submodules/PhotoResources/Sources/PhotoResources.swift index 5a04a76fb2..60e58770b5 100644 --- a/submodules/PhotoResources/Sources/PhotoResources.swift +++ b/submodules/PhotoResources/Sources/PhotoResources.swift @@ -27,16 +27,16 @@ public func largestRepresentationForPhoto(_ photo: TelegramMediaImage) -> Telegr return photo.representationForDisplayAtSize(PixelDimensions(width: 1280, height: 1280)) } -public func chatMessagePhotoDatas(postbox: Postbox, photoReference: ImageMediaReference, fullRepresentationSize: CGSize = CGSize(width: 1280.0, height: 1280.0), autoFetchFullSize: Bool = false, tryAdditionalRepresentations: Bool = false, synchronousLoad: Bool = false) -> Signal, NoError> { +public func chatMessagePhotoDatas(postbox: Postbox, photoReference: ImageMediaReference, fullRepresentationSize: CGSize = CGSize(width: 1280.0, height: 1280.0), autoFetchFullSize: Bool = false, tryAdditionalRepresentations: Bool = false, synchronousLoad: Bool = false) -> Signal, NoError> { if let smallestRepresentation = smallestImageRepresentation(photoReference.media.representations), let largestRepresentation = photoReference.media.representationForDisplayAtSize(PixelDimensions(width: Int32(fullRepresentationSize.width), height: Int32(fullRepresentationSize.height))) { let maybeFullSize = postbox.mediaBox.resourceData(largestRepresentation.resource, option: .complete(waitUntilFetchStatus: false), attemptSynchronously: synchronousLoad) let signal = maybeFullSize |> take(1) - |> mapToSignal { maybeData -> Signal, NoError> in + |> mapToSignal { maybeData -> Signal, NoError> in if maybeData.complete { let loadedData: Data? = try? Data(contentsOf: URL(fileURLWithPath: maybeData.path), options: []) - return .single(Tuple(nil, loadedData, true)) + return .single(Tuple(nil, loadedData, .full, true)) } else { let decodedThumbnailData = photoReference.media.immediateThumbnailData.flatMap(decodeTinyThumbnail) let fetchedThumbnail: Signal @@ -47,13 +47,20 @@ public func chatMessagePhotoDatas(postbox: Postbox, photoReference: ImageMediaRe } let fetchedFullSize = fetchedMediaResource(mediaBox: postbox.mediaBox, reference: photoReference.resourceReference(largestRepresentation.resource), statsCategory: .image) - let anyThumbnail: [Signal] + let anyThumbnail: [Signal<(MediaResourceData, ChatMessagePhotoQuality), NoError>] if tryAdditionalRepresentations { anyThumbnail = photoReference.media.representations.filter({ representation in return representation != largestRepresentation - }).map({ representation -> Signal in + }).map({ representation -> Signal<(MediaResourceData, ChatMessagePhotoQuality), NoError> in return postbox.mediaBox.resourceData(representation.resource) |> take(1) + |> map { data -> (MediaResourceData, ChatMessagePhotoQuality) in + if representation.dimensions.width > 200 || representation.dimensions.height > 200 { + return (data, .medium) + } else { + return (data, .blurred) + } + } }) } else { anyThumbnail = [] @@ -78,13 +85,16 @@ public func chatMessagePhotoDatas(postbox: Postbox, photoReference: ImageMediaRe } let thumbnail = combineLatest(anyThumbnail) - |> mapToSignal { thumbnails -> Signal in - for thumbnail in thumbnails { + |> mapToSignal { thumbnails -> Signal<(Data, ChatMessagePhotoQuality)?, NoError> in + for (thumbnail, quality) in thumbnails { if thumbnail.size != 0, let data = try? Data(contentsOf: URL(fileURLWithPath: thumbnail.path), options: []) { - return .single(data) + return .single((data, quality)) } } return mainThumbnail + |> map { data -> (Data, ChatMessagePhotoQuality)? in + return data.flatMap { ($0, .blurred) } + } } let fullSizeData: Signal, NoError> @@ -110,13 +120,13 @@ public func chatMessagePhotoDatas(postbox: Postbox, photoReference: ImageMediaRe return thumbnail |> mapToSignal { thumbnailData in - if let thumbnailData = thumbnailData { + if let (thumbnailData, thumbnailQuality) = thumbnailData { return fullSizeData |> map { value in - return Tuple(thumbnailData, value._0, value._1) + return Tuple(thumbnailData, value._0, value._1 ? .full : thumbnailQuality, value._1) } } else { - return .single(Tuple(thumbnailData, nil, false)) + return .single(Tuple(nil, nil, .none, false)) } } } @@ -405,7 +415,7 @@ public func rawMessagePhoto(postbox: Postbox, photoReference: ImageMediaReferenc |> map { value -> UIImage? in let thumbnailData = value._0 let fullSizeData = value._1 - let fullSizeComplete = value._2 + let fullSizeComplete = value._3 if let fullSizeData = fullSizeData { if fullSizeComplete { return UIImage(data: fullSizeData)?.precomposed() @@ -420,20 +430,28 @@ public func rawMessagePhoto(postbox: Postbox, photoReference: ImageMediaReferenc public func chatMessagePhoto(postbox: Postbox, photoReference: ImageMediaReference, synchronousLoad: Bool = false) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { return chatMessagePhotoInternal(photoData: chatMessagePhotoDatas(postbox: postbox, photoReference: photoReference, tryAdditionalRepresentations: true, synchronousLoad: synchronousLoad), synchronousLoad: synchronousLoad) - |> map { _, generate in + |> map { _, _, generate in return generate } } -public func chatMessagePhotoInternal(photoData: Signal, NoError>, synchronousLoad: Bool = false) -> Signal<(() -> CGSize?, (TransformImageArguments) -> DrawingContext?), NoError> { +public enum ChatMessagePhotoQuality { + case none + case blurred + case medium + case full +} + +public func chatMessagePhotoInternal(photoData: Signal, NoError>, synchronousLoad: Bool = false) -> Signal<(() -> CGSize?, ChatMessagePhotoQuality, (TransformImageArguments) -> DrawingContext?), NoError> { return photoData |> map { value in let thumbnailData = value._0 let fullSizeData = value._1 - let fullSizeComplete = value._2 + let quality = value._2 + let fullSizeComplete = value._3 return ({ return nil - }, { arguments in + }, quality, { arguments in let drawingRect = arguments.drawingRect var fittedSize = arguments.imageSize if abs(fittedSize.width - arguments.boundingSize.width).isLessThanOrEqualTo(CGFloat(1.0)) { @@ -853,7 +871,7 @@ public func chatSecretPhoto(account: Account, photoReference: ImageMediaReferenc |> map { value in let thumbnailData = value._0 let fullSizeData = value._1 - let fullSizeComplete = value._2 + let fullSizeComplete = value._3 return { arguments in let context = DrawingContext(size: arguments.drawingSize, clear: true) @@ -1114,7 +1132,7 @@ public func mediaGridMessagePhoto(account: Account, photoReference: ImageMediaRe |> map { value in let thumbnailData = value._0 let fullSizeData = value._1 - let fullSizeComplete = value._2 + let fullSizeComplete = value._3 return { arguments in let context = DrawingContext(size: arguments.drawingSize, clear: true) @@ -1275,7 +1293,7 @@ public func internalMediaGridMessageVideo(postbox: Postbox, videoReference: File |> map { value -> Tuple3?, Bool> in let thumbnailData = value._0 let fullSizeData = value._1 - let fullSizeComplete = value._2 + let fullSizeComplete = value._3 return Tuple(thumbnailData, fullSizeData.flatMap({ Tuple($0, "") }), fullSizeComplete) } } else { @@ -2131,34 +2149,16 @@ public func chatAvatarGalleryPhoto(account: Account, representations: [ImageRepr if let thumbnailImage = thumbnailImage { let thumbnailSize = CGSize(width: thumbnailImage.width, height: thumbnailImage.height) - let initialThumbnailContextFittingSize = fittedSize.fitted(CGSize(width: 90.0, height: 90.0)) + let initialThumbnailContextFittingSize = fittedSize.fitted(CGSize(width: 350.0, height: 350.0)) - let thumbnailContextSize = thumbnailSize.aspectFitted(initialThumbnailContextFittingSize) + let thumbnailContextSize = thumbnailSize.aspectFilled(initialThumbnailContextFittingSize) let thumbnailContext = DrawingContext(size: thumbnailContextSize, scale: 1.0) thumbnailContext.withFlippedContext { c in c.draw(thumbnailImage, in: CGRect(origin: CGPoint(), size: thumbnailContextSize)) } - telegramFastBlurMore(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes) + imageFastBlur(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes) - var thumbnailContextFittingSize = CGSize(width: floor(arguments.drawingSize.width * 0.5), height: floor(arguments.drawingSize.width * 0.5)) - if thumbnailContextFittingSize.width < 150.0 || thumbnailContextFittingSize.height < 150.0 { - thumbnailContextFittingSize = thumbnailContextFittingSize.aspectFilled(CGSize(width: 150.0, height: 150.0)) - } - - if false, thumbnailContextFittingSize.width > thumbnailContextSize.width { - let additionalContextSize = thumbnailContextFittingSize - let additionalBlurContext = DrawingContext(size: additionalContextSize, scale: 1.0) - additionalBlurContext.withFlippedContext { c in - c.interpolationQuality = .default - if let image = thumbnailContext.generateImage()?.cgImage { - c.draw(image, in: CGRect(origin: CGPoint(), size: additionalContextSize)) - } - } - imageFastBlur(Int32(additionalContextSize.width), Int32(additionalContextSize.height), Int32(additionalBlurContext.bytesPerRow), additionalBlurContext.bytes) - blurredThumbnailImage = additionalBlurContext.generateImage() - } else { - blurredThumbnailImage = thumbnailContext.generateImage() - } + blurredThumbnailImage = thumbnailContext.generateImage() } if let blurredThumbnailImage = blurredThumbnailImage, fullSizeImage == nil { diff --git a/submodules/Postbox/Sources/ChatListTable.swift b/submodules/Postbox/Sources/ChatListTable.swift index b44e99fc82..9252022dce 100644 --- a/submodules/Postbox/Sources/ChatListTable.swift +++ b/submodules/Postbox/Sources/ChatListTable.swift @@ -245,12 +245,31 @@ final class ChatListTable: Table { } } - func getUnreadChatListPeerIds(postbox: Postbox, groupId: PeerGroupId) -> [PeerId] { + func getUnreadChatListPeerIds(postbox: Postbox, groupId: PeerGroupId, filterPredicate: ChatListFilterPredicate?) -> [PeerId] { var result: [PeerId] = [] self.valueBox.range(self.table, start: self.upperBound(groupId: groupId), end: self.lowerBound(groupId: groupId), keys: { key in let (_, _, messageIndex, _) = extractKey(key) if let state = postbox.readStateTable.getCombinedState(messageIndex.id.peerId), state.isUnread { - result.append(messageIndex.id.peerId) + + let passFilter: Bool + if let filterPredicate = filterPredicate { + if let peer = postbox.peerTable.get(messageIndex.id.peerId) { + let isUnread = postbox.readStateTable.getCombinedState(messageIndex.id.peerId)?.isUnread ?? false + if filterPredicate.includes(peer: peer, notificationSettings: postbox.peerNotificationSettingsTable.getEffective(messageIndex.id.peerId), isUnread: isUnread) { + passFilter = true + } else { + passFilter = false + } + } else { + passFilter = false + } + } else { + passFilter = true + } + + if passFilter { + result.append(messageIndex.id.peerId) + } } return true }, limit: 0) @@ -651,6 +670,19 @@ final class ChatListTable: Table { } } + func getEntry(peerId: PeerId, messageHistoryTable: MessageHistoryTable, peerChatInterfaceStateTable: PeerChatInterfaceStateTable) -> ChatListIntermediateEntry? { + if let (peerGroupId, index) = self.getPeerChatListIndex(peerId: peerId) { + let key = self.key(groupId: peerGroupId, index: index, type: .message) + if let value = self.valueBox.get(self.table, key: key) { + return readEntry(groupId: peerGroupId, messageHistoryTable: messageHistoryTable, peerChatInterfaceStateTable: peerChatInterfaceStateTable, key: key, value: value) + } else { + return nil + } + } else { + return nil + } + } + func allEntries(groupId: PeerGroupId) -> [ChatListEntryInfo] { var entries: [ChatListEntryInfo] = [] self.valueBox.range(self.table, start: self.upperBound(groupId: groupId), end: self.lowerBound(groupId: groupId), values: { key, value in diff --git a/submodules/Postbox/Sources/ChatListView.swift b/submodules/Postbox/Sources/ChatListView.swift index 920d88b95b..aa70142924 100644 --- a/submodules/Postbox/Sources/ChatListView.swift +++ b/submodules/Postbox/Sources/ChatListView.swift @@ -295,19 +295,39 @@ private func updatedRenderedPeer(_ renderedPeer: RenderedPeer, updatedPeers: [Pe return nil } +public struct ChatListFilterPredicate { + public var includePeerIds: Set + public var include: (Peer, PeerNotificationSettings?, Bool) -> Bool + + public init(includePeerIds: Set, include: @escaping (Peer, PeerNotificationSettings?, Bool) -> Bool) { + self.includePeerIds = includePeerIds + self.include = include + } + + func includes(peer: Peer, notificationSettings: PeerNotificationSettings?, isUnread: Bool) -> Bool { + if self.includePeerIds.contains(peer.id) { + return true + } else { + return self.include(peer, notificationSettings, isUnread) + } + } +} + final class MutableChatListView { let groupId: PeerGroupId - let filterPredicate: ((Peer, PeerNotificationSettings?, Bool) -> Bool)? + let filterPredicate: ChatListFilterPredicate? private let summaryComponents: ChatListEntrySummaryComponents fileprivate var additionalItemIds: Set fileprivate var additionalItemEntries: [MutableChatListEntry] + fileprivate var additionalMixedItemIds: Set + fileprivate var additionalMixedItemEntries: [MutableChatListEntry] fileprivate var earlier: MutableChatListEntry? fileprivate var later: MutableChatListEntry? fileprivate var entries: [MutableChatListEntry] fileprivate var groupEntries: [ChatListGroupReferenceEntry] private var count: Int - init(postbox: Postbox, groupId: PeerGroupId, filterPredicate: ((Peer, PeerNotificationSettings?, Bool) -> Bool)?, aroundIndex: ChatListIndex, count: Int, summaryComponents: ChatListEntrySummaryComponents) { + init(postbox: Postbox, groupId: PeerGroupId, filterPredicate: ChatListFilterPredicate?, aroundIndex: ChatListIndex, count: Int, summaryComponents: ChatListEntrySummaryComponents) { let (entries, earlier, later) = postbox.fetchAroundChatEntries(groupId: groupId, index: aroundIndex, count: count, filterPredicate: filterPredicate) self.groupId = groupId @@ -318,7 +338,17 @@ final class MutableChatListView { self.count = count self.summaryComponents = summaryComponents self.additionalItemEntries = [] - if case .root = groupId { + self.additionalMixedItemEntries = [] + self.additionalMixedItemIds = Set() + if let filterPredicate = self.filterPredicate { + self.additionalMixedItemIds.formUnion(filterPredicate.includePeerIds) + } + for peerId in self.additionalMixedItemIds { + if let entry = postbox.chatListTable.getEntry(peerId: peerId, messageHistoryTable: postbox.messageHistoryTable, peerChatInterfaceStateTable: postbox.peerChatInterfaceStateTable) { + self.additionalMixedItemEntries.append(MutableChatListEntry(entry, cachedDataTable: postbox.cachedPeerDataTable, readStateTable: postbox.readStateTable, messageHistoryTable: postbox.messageHistoryTable)) + } + } + if case .root = groupId, self.filterPredicate == nil { let itemIds = postbox.additionalChatListItemsTable.get() self.additionalItemIds = Set(itemIds) for peerId in itemIds { @@ -336,7 +366,7 @@ final class MutableChatListView { private func reloadGroups(postbox: Postbox) { self.groupEntries.removeAll() - if case .root = self.groupId { + if case .root = self.groupId, self.filterPredicate == nil { for groupId in postbox.chatListTable.existingGroups() { var foundIndices: [(ChatListIndex, MessageIndex)] = [] var unpinnedCount = 0 @@ -436,27 +466,56 @@ final class MutableChatListView { if let groupOperations = operations[self.groupId] { for operation in groupOperations { switch operation { - case let .InsertEntry(index, message, combinedReadState, embeddedState): - if self.add(.IntermediateMessageEntry(index, message, combinedReadState, embeddedState), postbox: postbox) { - hasChanges = true - } - case let .InsertHole(index): - if self.add(.HoleEntry(index), postbox: postbox) { - hasChanges = true - } - case let .RemoveEntry(indices): - if self.remove(Set(indices), type: .message, context: context) { - hasChanges = true - } - case let .RemoveHoles(indices): - if self.remove(Set(indices), type: .hole, context: context) { - hasChanges = true - } + case let .InsertEntry(index, message, combinedReadState, embeddedState): + if self.add(.IntermediateMessageEntry(index, message, combinedReadState, embeddedState), postbox: postbox) { + hasChanges = true + } + case let .InsertHole(index): + if self.add(.HoleEntry(index), postbox: postbox) { + hasChanges = true + } + case let .RemoveEntry(indices): + if self.remove(Set(indices), type: .message, context: context) { + hasChanges = true + } + case let .RemoveHoles(indices): + if self.remove(Set(indices), type: .hole, context: context) { + hasChanges = true + } } } } - if case .root = self.groupId { + /*if let filterPredicate = self.filterPredicate, !filterPredicate.includePeerIds.isEmpty { + for (groupId, groupOperations) in operations { + if groupId == self.groupId { + continue + } + for operation in groupOperations { + switch operation { + case let .InsertEntry(index, message, combinedReadState, embeddedState): + if filterPredicate.includePeerIds.contains(index.messageIndex.id.peerId) { + if self.add(.IntermediateMessageEntry(index, message, combinedReadState, embeddedState), postbox: postbox) { + hasChanges = true + } + } + case .InsertHole: + break + case let .RemoveEntry(indices): + let updatedIndices = indices.filter { index in + return filterPredicate.includePeerIds.contains(index.messageIndex.id.peerId) + } + if !updatedIndices.isEmpty && self.remove(Set(updatedIndices), type: .message, context: context) { + hasChanges = true + } + case .RemoveHoles: + break + } + } + } + }*/ + + if case .root = self.groupId, self.filterPredicate == nil { var invalidatedGroups = false for (groupId, groupOperations) in operations { if case .group = groupId, !groupOperations.isEmpty { @@ -497,11 +556,17 @@ final class MutableChatListView { for (peerId, settingsChange) in updatedPeerNotificationSettings { if let peer = postbox.peerTable.get(peerId) { let isUnread = postbox.readStateTable.getCombinedState(peerId)?.isUnread ?? false - let wasIncluded = filterPredicate(peer, settingsChange.0, isUnread) - let isIncluded = filterPredicate(peer, settingsChange.1, isUnread) + let wasIncluded = filterPredicate.includes(peer: peer, notificationSettings: settingsChange.0, isUnread: isUnread) + let isIncluded = filterPredicate.includes(peer: peer, notificationSettings: settingsChange.1, isUnread: isUnread) if wasIncluded != isIncluded { if isIncluded { - if let entry = postbox.chatListTable.getEntry(groupId: self.groupId, peerId: peerId, messageHistoryTable: postbox.messageHistoryTable, peerChatInterfaceStateTable: postbox.peerChatInterfaceStateTable) { + let tableEntry: ChatListIntermediateEntry? + if filterPredicate.includePeerIds.contains(peerId) { + tableEntry = postbox.chatListTable.getEntry(peerId: peerId, messageHistoryTable: postbox.messageHistoryTable, peerChatInterfaceStateTable: postbox.peerChatInterfaceStateTable) + } else { + tableEntry = postbox.chatListTable.getEntry(groupId: self.groupId, peerId: peerId, messageHistoryTable: postbox.messageHistoryTable, peerChatInterfaceStateTable: postbox.peerChatInterfaceStateTable) + } + if let entry = tableEntry { switch entry { case let .message(index, message, embeddedState): let combinedReadState = postbox.readStateTable.getCombinedState(index.messageIndex.id.peerId) @@ -657,6 +722,27 @@ final class MutableChatListView { } hasChanges = true } + var updateAdditionalMixedItems = false + for peerId in self.additionalMixedItemIds { + if transaction.currentOperationsByPeerId[peerId] != nil { + updateAdditionalMixedItems = true + } + if transaction.currentUpdatedPeers[peerId] != nil { + updateAdditionalMixedItems = true + } + if transaction.currentUpdatedChatListInclusions[peerId] != nil { + updateAdditionalMixedItems = true + } + } + if updateAdditionalMixedItems { + self.additionalMixedItemEntries.removeAll() + for peerId in self.additionalMixedItemIds { + if let entry = postbox.chatListTable.getEntry(peerId: peerId, messageHistoryTable: postbox.messageHistoryTable, peerChatInterfaceStateTable: postbox.peerChatInterfaceStateTable) { + self.additionalMixedItemEntries.append(MutableChatListEntry(entry, cachedDataTable: postbox.cachedPeerDataTable, readStateTable: postbox.readStateTable, messageHistoryTable: postbox.messageHistoryTable)) + } + } + hasChanges = true + } return hasChanges } @@ -666,7 +752,7 @@ final class MutableChatListView { case .IntermediateMessageEntry(let index, _, _, _), .MessageEntry(let index, _, _, _, _, _, _, _, _): if let peer = postbox.peerTable.get(index.messageIndex.id.peerId) { let isUnread = postbox.readStateTable.getCombinedState(index.messageIndex.id.peerId)?.isUnread ?? false - if !filterPredicate(peer, postbox.peerNotificationSettingsTable.getEffective(index.messageIndex.id.peerId), isUnread) { + if !filterPredicate.includes(peer: peer, notificationSettings: postbox.peerNotificationSettingsTable.getEffective(index.messageIndex.id.peerId), isUnread: isUnread) { return false } } else { @@ -953,6 +1039,11 @@ final class MutableChatListView { self.additionalItemEntries[i] = updatedEntry } } + for i in 0 ..< self.additionalMixedItemEntries.count { + if let updatedEntry = self.renderEntry(self.additionalMixedItemEntries[i], postbox: postbox, renderMessage: renderMessage, getPeer: getPeer, getPeerNotificationSettings: getPeerNotificationSettings, getPeerPresence: getPeerPresence) { + self.additionalMixedItemEntries[i] = updatedEntry + } + } } } @@ -970,18 +1061,37 @@ public final class ChatListView { var entries: [ChatListEntry] = [] for entry in mutableView.entries { switch entry { - case let .MessageEntry(index, message, combinedReadState, notificationSettings, embeddedState, peer, peerPresence, summaryInfo, hasFailed): - entries.append(.MessageEntry(index, message, combinedReadState, notificationSettings, embeddedState, peer, peerPresence, summaryInfo, hasFailed)) - case let .HoleEntry(hole): - entries.append(.HoleEntry(hole)) - /*case let .GroupReferenceEntry(groupId, index, message, topPeers, counters): - entries.append(.GroupReferenceEntry(groupId, index, message, topPeers.getPeers(), GroupReferenceUnreadCounters(counters)))*/ - case .IntermediateMessageEntry: - assertionFailure() - /*case .IntermediateGroupReferenceEntry: - assertionFailure()*/ + case let .MessageEntry(index, message, combinedReadState, notificationSettings, embeddedState, peer, peerPresence, summaryInfo, hasFailed): + entries.append(.MessageEntry(index, message, combinedReadState, notificationSettings, embeddedState, peer, peerPresence, summaryInfo, hasFailed)) + case let .HoleEntry(hole): + entries.append(.HoleEntry(hole)) + case .IntermediateMessageEntry: + assertionFailure() } } + if !mutableView.additionalMixedItemEntries.isEmpty { + var existingIds = Set() + for entry in entries { + if case let .MessageEntry(messageEntry) = entry { + existingIds.insert(messageEntry.0.messageIndex.id.peerId) + } + } + for entry in mutableView.additionalMixedItemEntries { + if case let .MessageEntry(messageEntry) = entry { + if !existingIds.contains(messageEntry.0.messageIndex.id.peerId) { + switch entry { + case let .MessageEntry(index, message, combinedReadState, notificationSettings, embeddedState, peer, peerPresence, summaryInfo, hasFailed): + entries.append(.MessageEntry(index, message, combinedReadState, notificationSettings, embeddedState, peer, peerPresence, summaryInfo, hasFailed)) + case let .HoleEntry(hole): + entries.append(.HoleEntry(hole)) + case .IntermediateMessageEntry: + assertionFailure() + } + } + } + } + entries.sort() + } self.groupEntries = mutableView.groupEntries self.entries = entries self.earlierIndex = mutableView.earlier?.index diff --git a/submodules/Postbox/Sources/Postbox.swift b/submodules/Postbox/Sources/Postbox.swift index 31548f7f4f..7c837fcf1f 100644 --- a/submodules/Postbox/Sources/Postbox.swift +++ b/submodules/Postbox/Sources/Postbox.swift @@ -303,10 +303,10 @@ public final class Transaction { return self.postbox?.chatListTable.getPeerChatListIndex(peerId: peerId) } - public func getUnreadChatListPeerIds(groupId: PeerGroupId) -> [PeerId] { + public func getUnreadChatListPeerIds(groupId: PeerGroupId, filterPredicate: ChatListFilterPredicate?) -> [PeerId] { assert(!self.disposed) if let postbox = self.postbox { - return postbox.chatListTable.getUnreadChatListPeerIds(postbox: postbox, groupId: groupId) + return postbox.chatListTable.getUnreadChatListPeerIds(postbox: postbox, groupId: groupId, filterPredicate: filterPredicate) } else { return [] } @@ -1053,7 +1053,7 @@ public func openPostbox(basePath: String, seedConfiguration: SeedConfiguration, } public final class Postbox { - private let queue: Queue + public let queue: Queue public let seedConfiguration: SeedConfiguration private let basePath: String let valueBox: SqliteValueBox @@ -1653,13 +1653,13 @@ public final class Postbox { self.synchronizeGroupMessageStatsTable.set(groupId: groupId, namespace: namespace, needsValidation: false, operations: &self.currentUpdatedGroupSummarySynchronizeOperations) } - private func mappedChatListFilterPredicate(_ predicate: @escaping (Peer, PeerNotificationSettings?, Bool) -> Bool) -> (ChatListIntermediateEntry) -> Bool { + private func mappedChatListFilterPredicate(_ predicate: ChatListFilterPredicate) -> (ChatListIntermediateEntry) -> Bool { return { entry in switch entry { case let .message(index, _, _): if let peer = self.peerTable.get(index.messageIndex.id.peerId) { let isUnread = self.readStateTable.getCombinedState(index.messageIndex.id.peerId)?.isUnread ?? false - if predicate(peer, self.peerNotificationSettingsTable.getEffective(index.messageIndex.id.peerId), isUnread) { + if predicate.includes(peer: peer, notificationSettings: self.peerNotificationSettingsTable.getEffective(index.messageIndex.id.peerId), isUnread: isUnread) { return true } else { return false @@ -1673,7 +1673,7 @@ public final class Postbox { } } - func fetchAroundChatEntries(groupId: PeerGroupId, index: ChatListIndex, count: Int, filterPredicate: ((Peer, PeerNotificationSettings?, Bool) -> Bool)?) -> (entries: [MutableChatListEntry], earlier: MutableChatListEntry?, later: MutableChatListEntry?) { + func fetchAroundChatEntries(groupId: PeerGroupId, index: ChatListIndex, count: Int, filterPredicate: ChatListFilterPredicate?) -> (entries: [MutableChatListEntry], earlier: MutableChatListEntry?, later: MutableChatListEntry?) { let mappedPredicate = filterPredicate.flatMap(self.mappedChatListFilterPredicate) let (intermediateEntries, intermediateLower, intermediateUpper) = self.chatListTable.entriesAround(groupId: groupId, index: index, messageHistoryTable: self.messageHistoryTable, peerChatInterfaceStateTable: self.peerChatInterfaceStateTable, count: count, predicate: mappedPredicate) let entries: [MutableChatListEntry] = intermediateEntries.map { entry in @@ -1689,7 +1689,7 @@ public final class Postbox { return (entries, lower, upper) } - func fetchEarlierChatEntries(groupId: PeerGroupId, index: ChatListIndex?, count: Int, filterPredicate: ((Peer, PeerNotificationSettings?, Bool) -> Bool)?) -> [MutableChatListEntry] { + func fetchEarlierChatEntries(groupId: PeerGroupId, index: ChatListIndex?, count: Int, filterPredicate: ChatListFilterPredicate?) -> [MutableChatListEntry] { let mappedPredicate = filterPredicate.flatMap(self.mappedChatListFilterPredicate) let intermediateEntries = self.chatListTable.earlierEntries(groupId: groupId, index: index.flatMap({ ($0, true) }), messageHistoryTable: self.messageHistoryTable, peerChatInterfaceStateTable: self.peerChatInterfaceStateTable, count: count, predicate: mappedPredicate) let entries: [MutableChatListEntry] = intermediateEntries.map { entry in @@ -1698,7 +1698,7 @@ public final class Postbox { return entries } - func fetchLaterChatEntries(groupId: PeerGroupId, index: ChatListIndex?, count: Int, filterPredicate: ((Peer, PeerNotificationSettings?, Bool) -> Bool)?) -> [MutableChatListEntry] { + func fetchLaterChatEntries(groupId: PeerGroupId, index: ChatListIndex?, count: Int, filterPredicate: ChatListFilterPredicate?) -> [MutableChatListEntry] { let mappedPredicate = filterPredicate.flatMap(self.mappedChatListFilterPredicate) let intermediateEntries = self.chatListTable.laterEntries(groupId: groupId, index: index.flatMap({ ($0, true) }), messageHistoryTable: self.messageHistoryTable, peerChatInterfaceStateTable: self.peerChatInterfaceStateTable, count: count, predicate: mappedPredicate) let entries: [MutableChatListEntry] = intermediateEntries.map { entry in @@ -2546,11 +2546,11 @@ public final class Postbox { |> switchToLatest } - public func tailChatListView(groupId: PeerGroupId, filterPredicate: ((Peer, PeerNotificationSettings?, Bool) -> Bool)? = nil, count: Int, summaryComponents: ChatListEntrySummaryComponents) -> Signal<(ChatListView, ViewUpdateType), NoError> { + public func tailChatListView(groupId: PeerGroupId, filterPredicate: ChatListFilterPredicate? = nil, count: Int, summaryComponents: ChatListEntrySummaryComponents) -> Signal<(ChatListView, ViewUpdateType), NoError> { return self.aroundChatListView(groupId: groupId, filterPredicate: filterPredicate, index: ChatListIndex.absoluteUpperBound, count: count, summaryComponents: summaryComponents, userInteractive: true) } - public func aroundChatListView(groupId: PeerGroupId, filterPredicate: ((Peer, PeerNotificationSettings?, Bool) -> Bool)? = nil, index: ChatListIndex, count: Int, summaryComponents: ChatListEntrySummaryComponents, userInteractive: Bool = false) -> Signal<(ChatListView, ViewUpdateType), NoError> { + public func aroundChatListView(groupId: PeerGroupId, filterPredicate: ChatListFilterPredicate? = nil, index: ChatListIndex, count: Int, summaryComponents: ChatListEntrySummaryComponents, userInteractive: Bool = false) -> Signal<(ChatListView, ViewUpdateType), NoError> { return self.transactionSignal(userInteractive: userInteractive, { subscriber, transaction in let mutableView = MutableChatListView(postbox: self, groupId: groupId, filterPredicate: filterPredicate, aroundIndex: index, count: count, summaryComponents: summaryComponents) mutableView.render(postbox: self, renderMessage: self.renderIntermediateMessage, getPeer: { id in diff --git a/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsController.swift b/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsController.swift index 3c7774bcc4..6a28cfb012 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsController.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsController.swift @@ -767,7 +767,7 @@ func selectivePrivacySettingsController(context: AccountContext, kind: Selective return state } if peerIds.isEmpty { - let controller = context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: context, mode: .peerSelection(searchChatList: true, searchGroups: true), options: [])) + let controller = context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: context, mode: .peerSelection(searchChatList: true, searchGroups: true, searchChannels: false), options: [])) addPeerDisposable.set((controller.result |> take(1) |> deliverOnMainQueue).start(next: { [weak controller] peerIds in diff --git a/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsPeersController.swift b/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsPeersController.swift index 47875e24b9..e112201c40 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsPeersController.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsPeersController.swift @@ -290,7 +290,7 @@ public func selectivePrivacyPeersController(context: AccountContext, title: Stri removePeerDisposable.set(applyPeers.start()) }, addPeer: { - let controller = context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: context, mode: .peerSelection(searchChatList: true, searchGroups: true), options: [])) + let controller = context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: context, mode: .peerSelection(searchChatList: true, searchGroups: true, searchChannels: false), options: [])) addPeerDisposable.set((controller.result |> take(1) |> deliverOnMainQueue).start(next: { [weak controller] peerIds in diff --git a/submodules/SettingsUI/Sources/SettingsController.swift b/submodules/SettingsUI/Sources/SettingsController.swift index f9ed1cf03f..40d1114116 100644 --- a/submodules/SettingsUI/Sources/SettingsController.swift +++ b/submodules/SettingsUI/Sources/SettingsController.swift @@ -722,7 +722,7 @@ public protocol SettingsController: class { func updateContext(context: AccountContext) } -private final class SettingsControllerImpl: ItemListController, SettingsController, TabBarContainedController { +private final class SettingsControllerImpl: ItemListController, SettingsController { let sharedContext: SharedAccountContext let contextValue: Promise var accountsAndPeersValue: ((Account, Peer)?, [(Account, Peer, Int32)])? @@ -731,8 +731,6 @@ private final class SettingsControllerImpl: ItemListController, SettingsControll var switchToAccount: ((AccountRecordId) -> Void)? var addAccount: (() -> Void)? - weak var switchController: TabBarAccountSwitchController? - override var navigationBarRequiresEntireLayoutUpdate: Bool { return false } @@ -751,6 +749,8 @@ private final class SettingsControllerImpl: ItemListController, SettingsControll super.init(presentationData: ItemListPresentationData(presentationData), updatedPresentationData: updatedPresentationData |> map(ItemListPresentationData.init(_:)), state: state, tabBarItem: tabBarItem) + self.hasTabBarItemContextAction = true + self.accountsAndPeersDisposable = (accountsAndPeers |> deliverOnMainQueue).start(next: { [weak self] value in self?.accountsAndPeersValue = value @@ -769,7 +769,7 @@ private final class SettingsControllerImpl: ItemListController, SettingsControll //self.contextValue.set(.single(context)) } - func presentTabBarPreviewingController(sourceNodes: [ASDisplayNode]) { + /*func presentTabBarPreviewingController(sourceNodes: [ASDisplayNode]) { guard let (maybePrimary, other) = self.accountsAndPeersValue, let primary = maybePrimary else { return } @@ -778,11 +778,100 @@ private final class SettingsControllerImpl: ItemListController, SettingsControll }, addAccount: { [weak self] in self?.addAccount?() }, sourceNodes: sourceNodes) - self.switchController = controller self.sharedContext.mainWindow?.present(controller, on: .root) } func updateTabBarPreviewingControllerPresentation(_ update: TabBarContainedControllerPresentationUpdate) { + }*/ + + override public func tabBarItemContextAction(sourceNode: ContextExtractedContentContainingNode, gesture: ContextGesture) { + guard let (maybePrimary, other) = self.accountsAndPeersValue, let primary = maybePrimary else { + return + } + + let presentationData = self.sharedContext.currentPresentationData.with { $0 } + let strings = presentationData.strings + + var items: [ContextMenuItem] = [] + if other.count + 1 < maximumNumberOfAccounts { + items.append(.action(ContextMenuActionItem(text: strings.Settings_AddAccount, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Add"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] _, f in + guard let strongSelf = self else { + return + } + strongSelf.addAccount?() + f(.dismissWithoutContent) + }))) + } + + func accountIconSignal(account: Account, peer: Peer, size: CGSize) -> Signal { + let iconSignal: Signal + if let signal = peerAvatarImage(account: account, peerReference: PeerReference(peer), authorOfMessage: nil, representation: peer.profileImageRepresentations.first, displayDimensions: size, inset: 0.0, emptyColor: nil, synchronousLoad: false) { + iconSignal = signal + |> map { imageVersions -> UIImage? in + return imageVersions?.0 + } + } else { + let peerId = peer.id + let displayLetters = peer.displayLetters + iconSignal = Signal { subscriber in + let image = generateImage(size, rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + drawPeerAvatarLetters(context: context, size: CGSize(width: size.width, height: size.height), font: avatarFont, letters: displayLetters, peerId: peerId) + })?.withRenderingMode(.alwaysOriginal) + + subscriber.putNext(image) + subscriber.putCompletion() + return EmptyDisposable + } + } + return iconSignal + } + + let avatarSize = CGSize(width: 28.0, height: 28.0) + + items.append(.action(ContextMenuActionItem(text: primary.1.displayTitle(strings: strings, displayOrder: presentationData.nameDisplayOrder), icon: { _ in nil }, iconSource: ContextMenuActionItemIconSource(size: avatarSize, signal: accountIconSignal(account: primary.0, peer: primary.1, size: avatarSize)), action: { _, f in + f(.default) + }))) + + if !other.isEmpty { + items.append(.separator) + } + + for account in other { + let id = account.0.id + items.append(.action(ContextMenuActionItem(text: account.1.displayTitle(strings: strings, displayOrder: presentationData.nameDisplayOrder), badge: account.2 != 0 ? "\(account.2)" : "", icon: { _ in nil }, iconSource: ContextMenuActionItemIconSource(size: avatarSize, signal: accountIconSignal(account: account.0, peer: account.1, size: avatarSize)), action: { [weak self] _, f in + guard let strongSelf = self else { + return + } + strongSelf.switchToAccount?(id) + f(.dismissWithoutContent) + }))) + } + + let controller = ContextController(account: primary.0, presentationData: presentationData, source: .extracted(SettingsTabBarContextExtractedContentSource(controller: self, sourceNode: sourceNode)), items: .single(items), reactionItems: [], recognizer: nil, gesture: gesture) + self.sharedContext.mainWindow?.presentInGlobalOverlay(controller) + } +} + +private final class SettingsTabBarContextExtractedContentSource: ContextExtractedContentSource { + let keepInPlace: Bool = true + + private let controller: ViewController + private let sourceNode: ContextExtractedContentContainingNode + + init(controller: ViewController, sourceNode: ContextExtractedContentContainingNode) { + self.controller = controller + self.sourceNode = sourceNode + } + + func takeView() -> ContextControllerTakeViewInfo? { + return ContextControllerTakeViewInfo(contentContainingNode: self.sourceNode, contentAreaInScreenSpace: UIScreen.main.bounds) + } + + func putBack() -> ContextControllerPutBackViewInfo? { + return ContextControllerPutBackViewInfo(contentAreaInScreenSpace: UIScreen.main.bounds) } } @@ -1798,10 +1887,10 @@ private func accountContextMenuItems(context: AccountContext, logout: @escaping return context.account.postbox.transaction { transaction -> [ContextMenuItem] in var items: [ContextMenuItem] = [] - if !transaction.getUnreadChatListPeerIds(groupId: .root).isEmpty { + if !transaction.getUnreadChatListPeerIds(groupId: .root, filterPredicate: nil).isEmpty { items.append(.action(ContextMenuActionItem(text: strings.ChatList_Context_MarkAllAsRead, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/MarkAsRead"), color: theme.contextMenu.primaryColor) }, action: { _, f in let _ = (context.account.postbox.transaction { transaction in - markAllChatsAsReadInteractively(transaction: transaction, viewTracker: context.account.viewTracker, groupId: .root) + markAllChatsAsReadInteractively(transaction: transaction, viewTracker: context.account.viewTracker, groupId: .root, filterPredicate: nil) } |> deliverOnMainQueue).start(completed: { f(.default) diff --git a/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift b/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift index 675dc81df5..4588afd30c 100644 --- a/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift +++ b/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift @@ -215,7 +215,7 @@ private final class TextSizeSelectionControllerNode: ASDisplayNode, UIScrollView let interaction = ChatListNodeInteraction(activateSearch: {}, peerSelected: { _ in }, disabledPeerSelected: { _ in }, togglePeerSelected: { _ in }, messageSelected: { _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, deletePeer: { _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, activateChatPreview: { _, _, gesture in gesture?.cancel() - }) + }, present: { _ in }) let chatListPresentationData = ChatListPresentationData(theme: self.presentationData.theme, fontSize: self.presentationData.listsFontSize, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameSortOrder: self.presentationData.nameSortOrder, nameDisplayOrder: self.presentationData.nameDisplayOrder, disableAnimations: true) let peers = SimpleDictionary() @@ -233,22 +233,22 @@ private final class TextSizeSelectionControllerNode: ASDisplayNode, UIScrollView let timestamp = self.referenceTimestamp let timestamp1 = timestamp + 120 - items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, index: ChatListIndex(pinningIndex: 0, messageIndex: MessageIndex(id: MessageId(peerId: peer1.id, namespace: 0, id: 0), timestamp: timestamp1)), content: .peer(message: Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer1.id, namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp1, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: selfPeer, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_1_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), peer: RenderedPeer(peer: peer1), combinedReadState: CombinedPeerReadState(states: [(Namespaces.Message.Cloud, PeerReadState.idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: 0, markedUnread: false))]), notificationSettings: nil, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) + items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, isInFilter: false, index: ChatListIndex(pinningIndex: 0, messageIndex: MessageIndex(id: MessageId(peerId: peer1.id, namespace: 0, id: 0), timestamp: timestamp1)), content: .peer(message: Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer1.id, namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp1, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: selfPeer, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_1_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), peer: RenderedPeer(peer: peer1), combinedReadState: CombinedPeerReadState(states: [(Namespaces.Message.Cloud, PeerReadState.idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: 0, markedUnread: false))]), notificationSettings: nil, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) let presenceTimestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970 + 60 * 60) let timestamp2 = timestamp + 3660 - items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer2.id, namespace: 0, id: 0), timestamp: timestamp2)), content: .peer(message: Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer2.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp2, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer2, text: "", attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), peer: RenderedPeer(peer: peer2), combinedReadState: nil, notificationSettings: nil, presence: TelegramUserPresence(status: .present(until: presenceTimestamp), lastActivity: presenceTimestamp), summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: [(peer2, .typingText)], isAd: false, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) + items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, isInFilter: false, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer2.id, namespace: 0, id: 0), timestamp: timestamp2)), content: .peer(message: Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer2.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp2, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer2, text: "", attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), peer: RenderedPeer(peer: peer2), combinedReadState: nil, notificationSettings: nil, presence: TelegramUserPresence(status: .present(until: presenceTimestamp), lastActivity: presenceTimestamp), summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: [(peer2, .typingText)], isAd: false, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) let timestamp3 = timestamp + 3200 - items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer3.id, namespace: 0, id: 0), timestamp: timestamp3)), content: .peer(message: Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer3.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp3, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer3Author, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_3_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), peer: RenderedPeer(peer: peer3), combinedReadState: nil, notificationSettings: nil, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) + items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, isInFilter: false, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer3.id, namespace: 0, id: 0), timestamp: timestamp3)), content: .peer(message: Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer3.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp3, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer3Author, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_3_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), peer: RenderedPeer(peer: peer3), combinedReadState: nil, notificationSettings: nil, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) let timestamp4 = timestamp + 3000 - items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer4.id, namespace: 0, id: 0), timestamp: timestamp4)), content: .peer(message: Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer4.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp4, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer4, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_4_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), peer: RenderedPeer(peer: peer4), combinedReadState: nil, notificationSettings: nil, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) + items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, isInFilter: false, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer4.id, namespace: 0, id: 0), timestamp: timestamp4)), content: .peer(message: Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer4.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp4, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer4, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_4_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), peer: RenderedPeer(peer: peer4), combinedReadState: nil, notificationSettings: nil, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) let timestamp5 = timestamp + 1000 - items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer5.id, namespace: 0, id: 0), timestamp: timestamp5)), content: .peer(message: Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer4.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp5, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer5, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_5_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), peer: RenderedPeer(peer: peer5), combinedReadState: nil, notificationSettings: nil, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) + items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, isInFilter: false, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer5.id, namespace: 0, id: 0), timestamp: timestamp5)), content: .peer(message: Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer4.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp5, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer5, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_5_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), peer: RenderedPeer(peer: peer5), combinedReadState: nil, notificationSettings: nil, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) - items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer6.id, namespace: 0, id: 0), timestamp: timestamp - 360)), content: .peer(message: Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer6.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp - 360, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer6, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_6_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), peer: RenderedPeer(peer: peer6), combinedReadState: CombinedPeerReadState(states: [(Namespaces.Message.Cloud, PeerReadState.idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: 1, markedUnread: false))]), notificationSettings: nil, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) + items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, isInFilter: false, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer6.id, namespace: 0, id: 0), timestamp: timestamp - 360)), content: .peer(message: Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer6.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp - 360, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer6, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_6_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), peer: RenderedPeer(peer: peer6), combinedReadState: CombinedPeerReadState(states: [(Namespaces.Message.Cloud, PeerReadState.idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: 1, markedUnread: false))]), notificationSettings: nil, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) let width: CGFloat if case .regular = layout.metrics.widthClass { diff --git a/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift b/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift index 57916acc46..809fe525a0 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift @@ -768,6 +768,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate let interaction = ChatListNodeInteraction(activateSearch: {}, peerSelected: { _ in }, disabledPeerSelected: { _ in }, togglePeerSelected: { _ in }, messageSelected: { _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, deletePeer: { _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, activateChatPreview: { _, _, gesture in gesture?.cancel() + }, present: { _ in }) let chatListPresentationData = ChatListPresentationData(theme: self.theme, fontSize: self.presentationData.listsFontSize, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameSortOrder: self.presentationData.nameSortOrder, nameDisplayOrder: self.presentationData.nameDisplayOrder, disableAnimations: true) @@ -783,17 +784,17 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate let timestamp = self.referenceTimestamp let timestamp1 = timestamp + 120 - items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, index: ChatListIndex(pinningIndex: 0, messageIndex: MessageIndex(id: MessageId(peerId: peer1.id, namespace: 0, id: 0), timestamp: timestamp1)), content: .peer(message: Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer1.id, namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp1, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: selfPeer, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_1_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), peer: RenderedPeer(peer: peer1), combinedReadState: CombinedPeerReadState(states: [(Namespaces.Message.Cloud, PeerReadState.idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: 0, markedUnread: false))]), notificationSettings: nil, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) + items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, isInFilter: false, index: ChatListIndex(pinningIndex: 0, messageIndex: MessageIndex(id: MessageId(peerId: peer1.id, namespace: 0, id: 0), timestamp: timestamp1)), content: .peer(message: Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer1.id, namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp1, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: selfPeer, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_1_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), peer: RenderedPeer(peer: peer1), combinedReadState: CombinedPeerReadState(states: [(Namespaces.Message.Cloud, PeerReadState.idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: 0, markedUnread: false))]), notificationSettings: nil, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) let presenceTimestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970 + 60 * 60) let timestamp2 = timestamp + 3660 - items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer2.id, namespace: 0, id: 0), timestamp: timestamp2)), content: .peer(message: Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer2.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp2, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer2, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_2_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), peer: RenderedPeer(peer: peer2), combinedReadState: CombinedPeerReadState(states: [(Namespaces.Message.Cloud, PeerReadState.idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: 1, markedUnread: false))]), notificationSettings: nil, presence: TelegramUserPresence(status: .present(until: presenceTimestamp), lastActivity: presenceTimestamp), summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) + items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, isInFilter: false, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer2.id, namespace: 0, id: 0), timestamp: timestamp2)), content: .peer(message: Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer2.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp2, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer2, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_2_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), peer: RenderedPeer(peer: peer2), combinedReadState: CombinedPeerReadState(states: [(Namespaces.Message.Cloud, PeerReadState.idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: 1, markedUnread: false))]), notificationSettings: nil, presence: TelegramUserPresence(status: .present(until: presenceTimestamp), lastActivity: presenceTimestamp), summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) let timestamp3 = timestamp + 3200 - items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer3.id, namespace: 0, id: 0), timestamp: timestamp3)), content: .peer(message: Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer3.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp3, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer3Author, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_3_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), peer: RenderedPeer(peer: peer3), combinedReadState: nil, notificationSettings: nil, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) + items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, isInFilter: false, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer3.id, namespace: 0, id: 0), timestamp: timestamp3)), content: .peer(message: Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer3.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp3, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer3Author, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_3_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), peer: RenderedPeer(peer: peer3), combinedReadState: nil, notificationSettings: nil, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) let timestamp4 = timestamp + 3000 - items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer4.id, namespace: 0, id: 0), timestamp: timestamp4)), content: .peer(message: Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer4.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp4, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer4, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_4_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), peer: RenderedPeer(peer: peer4), combinedReadState: nil, notificationSettings: nil, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) + items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, isInFilter: false, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer4.id, namespace: 0, id: 0), timestamp: timestamp4)), content: .peer(message: Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer4.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp4, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer4, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_4_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), peer: RenderedPeer(peer: peer4), combinedReadState: nil, notificationSettings: nil, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) let params = ListViewItemLayoutParams(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, availableHeight: layout.size.height) if let chatNodes = self.chatNodes { diff --git a/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift b/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift index 469477a0d6..631065c3c6 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift @@ -352,6 +352,7 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate { let interaction = ChatListNodeInteraction(activateSearch: {}, peerSelected: { _ in }, disabledPeerSelected: { _ in }, togglePeerSelected: { _ in }, messageSelected: { _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, deletePeer: { _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, activateChatPreview: { _, _, gesture in gesture?.cancel() + }, present: { _ in }) let chatListPresentationData = ChatListPresentationData(theme: self.previewTheme, fontSize: self.presentationData.listsFontSize, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameSortOrder: self.presentationData.nameSortOrder, nameDisplayOrder: self.presentationData.nameDisplayOrder, disableAnimations: true) @@ -370,24 +371,24 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate { let timestamp = self.referenceTimestamp let timestamp1 = timestamp + 120 - items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, index: ChatListIndex(pinningIndex: 0, messageIndex: MessageIndex(id: MessageId(peerId: peer1.id, namespace: 0, id: 0), timestamp: timestamp1)), content: .peer(message: Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer1.id, namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp1, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: selfPeer, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_1_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), peer: RenderedPeer(peer: peer1), combinedReadState: CombinedPeerReadState(states: [(Namespaces.Message.Cloud, PeerReadState.idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: 0, markedUnread: false))]), notificationSettings: nil, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) + items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, isInFilter: false, index: ChatListIndex(pinningIndex: 0, messageIndex: MessageIndex(id: MessageId(peerId: peer1.id, namespace: 0, id: 0), timestamp: timestamp1)), content: .peer(message: Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer1.id, namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp1, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: selfPeer, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_1_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), peer: RenderedPeer(peer: peer1), combinedReadState: CombinedPeerReadState(states: [(Namespaces.Message.Cloud, PeerReadState.idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: 0, markedUnread: false))]), notificationSettings: nil, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) let presenceTimestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970 + 60 * 60) let timestamp2 = timestamp + 3660 - items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer2.id, namespace: 0, id: 0), timestamp: timestamp2)), content: .peer(message: Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer2.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp2, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer2, text: "", attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), peer: RenderedPeer(peer: peer2), combinedReadState: nil, notificationSettings: nil, presence: TelegramUserPresence(status: .present(until: presenceTimestamp), lastActivity: presenceTimestamp), summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: [(peer2, .typingText)], isAd: false, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) + items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, isInFilter: false, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer2.id, namespace: 0, id: 0), timestamp: timestamp2)), content: .peer(message: Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer2.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp2, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer2, text: "", attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), peer: RenderedPeer(peer: peer2), combinedReadState: nil, notificationSettings: nil, presence: TelegramUserPresence(status: .present(until: presenceTimestamp), lastActivity: presenceTimestamp), summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: [(peer2, .typingText)], isAd: false, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) let timestamp3 = timestamp + 3200 - items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer3.id, namespace: 0, id: 0), timestamp: timestamp3)), content: .peer(message: Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer3.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp3, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer3Author, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_3_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), peer: RenderedPeer(peer: peer3), combinedReadState: nil, notificationSettings: nil, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) + items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, isInFilter: false, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer3.id, namespace: 0, id: 0), timestamp: timestamp3)), content: .peer(message: Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer3.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp3, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer3Author, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_3_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), peer: RenderedPeer(peer: peer3), combinedReadState: nil, notificationSettings: nil, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) let timestamp4 = timestamp + 3000 - items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer4.id, namespace: 0, id: 0), timestamp: timestamp4)), content: .peer(message: Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer4.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp4, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer4, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_4_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), peer: RenderedPeer(peer: peer4), combinedReadState: nil, notificationSettings: nil, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) + items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, isInFilter: false, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer4.id, namespace: 0, id: 0), timestamp: timestamp4)), content: .peer(message: Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer4.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp4, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer4, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_4_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), peer: RenderedPeer(peer: peer4), combinedReadState: nil, notificationSettings: nil, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) let timestamp5 = timestamp + 1000 - items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer5.id, namespace: 0, id: 0), timestamp: timestamp5)), content: .peer(message: Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer4.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp5, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer5, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_5_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), peer: RenderedPeer(peer: peer5), combinedReadState: nil, notificationSettings: nil, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) + items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, isInFilter: false, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer5.id, namespace: 0, id: 0), timestamp: timestamp5)), content: .peer(message: Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer4.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp5, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer5, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_5_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), peer: RenderedPeer(peer: peer5), combinedReadState: nil, notificationSettings: nil, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) - items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer6.id, namespace: 0, id: 0), timestamp: timestamp - 360)), content: .peer(message: Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer6.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp - 360, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer6, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_6_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), peer: RenderedPeer(peer: peer6), combinedReadState: CombinedPeerReadState(states: [(Namespaces.Message.Cloud, PeerReadState.idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: 1, markedUnread: false))]), notificationSettings: nil, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) + items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, isInFilter: false, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer6.id, namespace: 0, id: 0), timestamp: timestamp - 360)), content: .peer(message: Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer6.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp - 360, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer6, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_6_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), peer: RenderedPeer(peer: peer6), combinedReadState: CombinedPeerReadState(states: [(Namespaces.Message.Cloud, PeerReadState.idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: 1, markedUnread: false))]), notificationSettings: nil, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) - items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer7.id, namespace: 0, id: 0), timestamp: timestamp - 420)), content: .peer(message: Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer7.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp - 420, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer6, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_7_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), peer: RenderedPeer(peer: peer7), combinedReadState: nil, notificationSettings: nil, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) + items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, isInFilter: false, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer7.id, namespace: 0, id: 0), timestamp: timestamp - 420)), content: .peer(message: Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer7.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp - 420, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer6, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_7_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), peer: RenderedPeer(peer: peer7), combinedReadState: nil, notificationSettings: nil, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) let width: CGFloat if case .regular = layout.metrics.widthClass { diff --git a/submodules/StatisticsUI/Sources/StatsController.swift b/submodules/StatisticsUI/Sources/StatsController.swift index e24f2dc32b..87b59f7d15 100644 --- a/submodules/StatisticsUI/Sources/StatsController.swift +++ b/submodules/StatisticsUI/Sources/StatsController.swift @@ -8,14 +8,19 @@ import SyncCore import MapKit import TelegramPresentationData import TelegramUIPreferences +import TelegramStringFormatting import ItemListUI import PresentationDataUtils import AccountContext import PresentationDataUtils import AppBundle +import Charts -private final class StatsArguments { - init() { +private final class StatsControllerArguments { + let loadDetailedGraph: (ChannelStatsGraph, Int64) -> Signal + + init(loadDetailedGraph: @escaping (ChannelStatsGraph, Int64) -> Signal) { + self.loadDetailedGraph = loadDetailedGraph } } @@ -32,32 +37,32 @@ private enum StatsSection: Int32 { } private enum StatsEntry: ItemListNodeEntry { - case overviewHeader(PresentationTheme, String) + case overviewHeader(PresentationTheme, String, String) case overview(PresentationTheme, ChannelStats) case growthTitle(PresentationTheme, String) - case growthGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, String, ChannelStatsGraph) + case growthGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, ChannelStatsGraph, ChartType) case followersTitle(PresentationTheme, String) - case followersGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, String, ChannelStatsGraph) + case followersGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, ChannelStatsGraph, ChartType) case notificationsTitle(PresentationTheme, String) - case notificationsGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, String, ChannelStatsGraph) + case notificationsGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, ChannelStatsGraph, ChartType) case viewsByHourTitle(PresentationTheme, String) - case viewsByHourGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, String, ChannelStatsGraph) + case viewsByHourGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, ChannelStatsGraph, ChartType) case postInteractionsTitle(PresentationTheme, String) - case postInteractionsGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, String, ChannelStatsGraph) + case postInteractionsGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, ChannelStatsGraph, ChartType) case viewsBySourceTitle(PresentationTheme, String) - case viewsBySourceGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, String, ChannelStatsGraph) + case viewsBySourceGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, ChannelStatsGraph, ChartType) case followersBySourceTitle(PresentationTheme, String) - case followersBySourceGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, String, ChannelStatsGraph) + case followersBySourceGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, ChannelStatsGraph, ChartType) case languagesTitle(PresentationTheme, String) - case languagesGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, String, ChannelStatsGraph) + case languagesGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, ChannelStatsGraph, ChartType) var section: ItemListSectionId { switch self { @@ -125,8 +130,8 @@ private enum StatsEntry: ItemListNodeEntry { static func ==(lhs: StatsEntry, rhs: StatsEntry) -> Bool { switch lhs { - case let .overviewHeader(lhsTheme, lhsText): - if case let .overviewHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { + case let .overviewHeader(lhsTheme, lhsText, lhsDates): + if case let .overviewHeader(rhsTheme, rhsText, rhsDates) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsDates == rhsDates { return true } else { return false @@ -143,8 +148,8 @@ private enum StatsEntry: ItemListNodeEntry { } else { return false } - case let .growthGraph(lhsTheme, lhsStrings, lhsDateTimeFormat, lhsText, lhsGraph): - if case let .growthGraph(rhsTheme, rhsStrings, rhsDateTimeFormat, rhsText, rhsGraph) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsText == rhsText, lhsGraph == rhsGraph { + case let .growthGraph(lhsTheme, lhsStrings, lhsDateTimeFormat, lhsGraph, lhsType): + if case let .growthGraph(rhsTheme, rhsStrings, rhsDateTimeFormat, rhsGraph, rhsType) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsGraph == rhsGraph, lhsType == rhsType { return true } else { return false @@ -155,8 +160,8 @@ private enum StatsEntry: ItemListNodeEntry { } else { return false } - case let .followersGraph(lhsTheme, lhsStrings, lhsDateTimeFormat, lhsText, lhsGraph): - if case let .followersGraph(rhsTheme, rhsStrings, rhsDateTimeFormat, rhsText, rhsGraph) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsText == rhsText, lhsGraph == rhsGraph { + case let .followersGraph(lhsTheme, lhsStrings, lhsDateTimeFormat, lhsGraph, lhsType): + if case let .followersGraph(rhsTheme, rhsStrings, rhsDateTimeFormat, rhsGraph, rhsType) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsGraph == rhsGraph, lhsType == rhsType { return true } else { return false @@ -167,8 +172,8 @@ private enum StatsEntry: ItemListNodeEntry { } else { return false } - case let .notificationsGraph(lhsTheme, lhsStrings, lhsDateTimeFormat, lhsText, lhsGraph): - if case let .notificationsGraph(rhsTheme, rhsStrings, rhsDateTimeFormat, rhsText, rhsGraph) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsText == rhsText, lhsGraph == rhsGraph { + case let .notificationsGraph(lhsTheme, lhsStrings, lhsDateTimeFormat, lhsGraph, lhsType): + if case let .notificationsGraph(rhsTheme, rhsStrings, rhsDateTimeFormat, rhsGraph, rhsType) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsGraph == rhsGraph, lhsType == rhsType { return true } else { return false @@ -179,8 +184,8 @@ private enum StatsEntry: ItemListNodeEntry { } else { return false } - case let .viewsByHourGraph(lhsTheme, lhsStrings, lhsDateTimeFormat, lhsText, lhsGraph): - if case let .viewsByHourGraph(rhsTheme, rhsStrings, rhsDateTimeFormat, rhsText, rhsGraph) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsText == rhsText, lhsGraph == rhsGraph { + case let .viewsByHourGraph(lhsTheme, lhsStrings, lhsDateTimeFormat, lhsGraph, lhsType): + if case let .viewsByHourGraph(rhsTheme, rhsStrings, rhsDateTimeFormat, rhsGraph, rhsType) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsGraph == rhsGraph, lhsType == rhsType { return true } else { return false @@ -191,8 +196,8 @@ private enum StatsEntry: ItemListNodeEntry { } else { return false } - case let .postInteractionsGraph(lhsTheme, lhsStrings, lhsDateTimeFormat, lhsText, lhsGraph): - if case let .postInteractionsGraph(rhsTheme, rhsStrings, rhsDateTimeFormat, rhsText, rhsGraph) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsText == rhsText, lhsGraph == rhsGraph { + case let .postInteractionsGraph(lhsTheme, lhsStrings, lhsDateTimeFormat, lhsGraph, lhsType): + if case let .postInteractionsGraph(rhsTheme, rhsStrings, rhsDateTimeFormat, rhsGraph, rhsType) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsGraph == rhsGraph, lhsType == rhsType { return true } else { return false @@ -203,8 +208,8 @@ private enum StatsEntry: ItemListNodeEntry { } else { return false } - case let .viewsBySourceGraph(lhsTheme, lhsStrings, lhsDateTimeFormat, lhsText, lhsGraph): - if case let .viewsBySourceGraph(rhsTheme, rhsStrings, rhsDateTimeFormat, rhsText, rhsGraph) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsText == rhsText, lhsGraph == rhsGraph { + case let .viewsBySourceGraph(lhsTheme, lhsStrings, lhsDateTimeFormat, lhsGraph, lhsType): + if case let .viewsBySourceGraph(rhsTheme, rhsStrings, rhsDateTimeFormat, rhsGraph, rhsType) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsGraph == rhsGraph, lhsType == rhsType { return true } else { return false @@ -215,8 +220,8 @@ private enum StatsEntry: ItemListNodeEntry { } else { return false } - case let .followersBySourceGraph(lhsTheme, lhsStrings, lhsDateTimeFormat, lhsText, lhsGraph): - if case let .followersBySourceGraph(rhsTheme, rhsStrings, rhsDateTimeFormat, rhsText, rhsGraph) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsText == rhsText, lhsGraph == rhsGraph { + case let .followersBySourceGraph(lhsTheme, lhsStrings, lhsDateTimeFormat, lhsGraph, lhsType): + if case let .followersBySourceGraph(rhsTheme, rhsStrings, rhsDateTimeFormat, rhsGraph, rhsType) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsGraph == rhsGraph, lhsType == rhsType { return true } else { return false @@ -227,8 +232,8 @@ private enum StatsEntry: ItemListNodeEntry { } else { return false } - case let .languagesGraph(lhsTheme, lhsStrings, lhsDateTimeFormat, lhsText, lhsGraph): - if case let .languagesGraph(rhsTheme, rhsStrings, rhsDateTimeFormat, rhsText, rhsGraph) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsText == rhsText, lhsGraph == rhsGraph { + case let .languagesGraph(lhsTheme, lhsStrings, lhsDateTimeFormat, lhsGraph, lhsType): + if case let .languagesGraph(rhsTheme, rhsStrings, rhsDateTimeFormat, rhsGraph, rhsType) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsGraph == rhsGraph, lhsType == rhsType { return true } else { return false @@ -241,9 +246,11 @@ private enum StatsEntry: ItemListNodeEntry { } func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem { + let arguments = arguments as! StatsControllerArguments switch self { - case let .overviewHeader(theme, text), - let .growthTitle(theme, text), + case let .overviewHeader(theme, text, dates): + return ItemListSectionHeaderItem(presentationData: presentationData, text: text, accessoryText: ItemListSectionHeaderAccessoryText(value: dates, color: .generic), sectionId: self.section) + case let .growthTitle(theme, text), let .followersTitle(theme, text), let .notificationsTitle(theme, text), let .viewsByHourTitle(theme, text), @@ -254,15 +261,24 @@ private enum StatsEntry: ItemListNodeEntry { return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) case let .overview(theme, stats): return StatsOverviewItem(presentationData: presentationData, stats: stats, sectionId: self.section, style: .blocks) - case let .growthGraph(theme, strings, dateTimeFormat, title, graph), - let .followersGraph(theme, strings, dateTimeFormat, title, graph), - let .notificationsGraph(theme, strings, dateTimeFormat, title, graph), - let .viewsByHourGraph(theme, strings, dateTimeFormat, title, graph), - let .postInteractionsGraph(theme, strings, dateTimeFormat, title, graph), - let .viewsBySourceGraph(theme, strings, dateTimeFormat, title, graph), - let .followersBySourceGraph(theme, strings, dateTimeFormat, title, graph), - let .languagesGraph(theme, strings, dateTimeFormat, title, graph): - return StatsGraphItem(presentationData: presentationData, title: title, graph: graph, sectionId: self.section, style: .blocks) + case let .growthGraph(theme, strings, dateTimeFormat, graph, type): + return StatsGraphItem(presentationData: presentationData, graph: graph, type: type, sectionId: self.section, style: .blocks) + case let .followersGraph(theme, strings, dateTimeFormat, graph, type): + return StatsGraphItem(presentationData: presentationData, graph: graph, type: type, sectionId: self.section, style: .blocks) + case let .notificationsGraph(theme, strings, dateTimeFormat, graph, type): + return StatsGraphItem(presentationData: presentationData, graph: graph, type: type, sectionId: self.section, style: .blocks) + case let .viewsByHourGraph(theme, strings, dateTimeFormat, graph, type): + return StatsGraphItem(presentationData: presentationData, graph: graph, type: type, sectionId: self.section, style: .blocks) + case let .postInteractionsGraph(theme, strings, dateTimeFormat, graph, type): + return StatsGraphItem(presentationData: presentationData, graph: graph, type: type, getDetailsData: { date, completion in + let _ = arguments.loadDetailedGraph(graph, Int64(date.timeIntervalSince1970)) + }, sectionId: self.section, style: .blocks) + case let .viewsBySourceGraph(theme, strings, dateTimeFormat, graph, type): + return StatsGraphItem(presentationData: presentationData, graph: graph, type: type, height: 160.0, sectionId: self.section, style: .blocks) + case let .followersBySourceGraph(theme, strings, dateTimeFormat, graph, type): + return StatsGraphItem(presentationData: presentationData, graph: graph, type: type, height: 160.0, sectionId: self.section, style: .blocks) + case let .languagesGraph(theme, strings, dateTimeFormat, graph, type): + return StatsGraphItem(presentationData: presentationData, graph: graph, type: type, height: 100.0, sectionId: self.section, style: .blocks) } } } @@ -271,17 +287,32 @@ private func statsControllerEntries(data: ChannelStats?, presentationData: Prese var entries: [StatsEntry] = [] if let data = data { - entries.append(.overviewHeader(presentationData.theme, "OVERVIEW")) + let minDate = stringForDate(timestamp: data.period.minDate, strings: presentationData.strings) + let maxDate = stringForDate(timestamp: data.period.maxDate, strings: presentationData.strings) + + entries.append(.overviewHeader(presentationData.theme, presentationData.strings.Stats_Overview, "\(minDate) – \(maxDate)")) entries.append(.overview(presentationData.theme, data)) - entries.append(.growthTitle(presentationData.theme, "GROWTH")) - entries.append(.growthGraph(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, "Growth", data.growthGraph)) + entries.append(.growthTitle(presentationData.theme, presentationData.strings.Stats_GrowthTitle)) + entries.append(.growthGraph(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, data.growthGraph, .lines)) - entries.append(.followersTitle(presentationData.theme, "FOLLOWERS")) - entries.append(.followersGraph(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, "Followers", data.followersGraph)) + entries.append(.followersTitle(presentationData.theme, presentationData.strings.Stats_FollowersTitle)) + entries.append(.followersGraph(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, data.followersGraph, .lines)) - entries.append(.notificationsTitle(presentationData.theme, "NOTIFICATIONS")) - entries.append(.notificationsGraph(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, "Notifications", data.muteGraph)) + entries.append(.notificationsTitle(presentationData.theme, presentationData.strings.Stats_NotificationsTitle)) + entries.append(.notificationsGraph(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, data.muteGraph, .lines)) + + entries.append(.postInteractionsTitle(presentationData.theme, presentationData.strings.Stats_InteractionsTitle)) + entries.append(.postInteractionsGraph(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, data.interactionsGraph, .twoAxis)) + + entries.append(.viewsBySourceTitle(presentationData.theme, presentationData.strings.Stats_ViewsBySourceTitle)) + entries.append(.viewsBySourceGraph(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, data.viewsBySourceGraph, .bars)) + + entries.append(.followersBySourceTitle(presentationData.theme, presentationData.strings.Stats_FollowersBySourceTitle)) + entries.append(.followersBySourceGraph(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, data.newFollowersBySourceGraph, .bars)) + + entries.append(.languagesTitle(presentationData.theme, presentationData.strings.Stats_LanguagesTitle)) + entries.append(.languagesGraph(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, data.languagesGraph, .pie)) } return entries @@ -303,7 +334,7 @@ public func channelStatsController(context: AccountContext, peer: Peer, cachedPe datacenterId = cachedData.statsDatacenterId } - let statsContext = ChannelStatsContext(network: context.account.network, datacenterId: datacenterId, peer: peer) + let statsContext = ChannelStatsContext(postbox: context.account.postbox, network: context.account.network, datacenterId: datacenterId, peerId: peer.id) let dataSignal: Signal = statsContext.state |> map { state in return state.stats @@ -311,18 +342,28 @@ public func channelStatsController(context: AccountContext, peer: Peer, cachedPe if let w = statsContext, let a = a { if case .OnDemand = a.interactionsGraph { w.loadInteractionsGraph() + w.loadNewFollowersBySourceGraph() + w.loadViewsBySourceGraph() + w.loadLanguagesGraph() } } }) dataPromise.set(.single(nil) |> then(dataSignal)) - let arguments = StatsArguments() + let arguments = StatsControllerArguments(loadDetailedGraph: { graph, x -> Signal in + return statsContext.loadDetailedGraph(graph, x: x) + }) let signal = combineLatest(context.sharedContext.presentationData, dataPromise.get()) |> deliverOnMainQueue |> map { presentationData, data -> (ItemListControllerState, (ItemListNodeState, Any)) in + var emptyStateItem: ItemListControllerEmptyStateItem? + if data == nil { + emptyStateItem = ItemListLoadingIndicatorEmptyStateItem(theme: presentationData.theme) + } + let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.ChannelInfo_Stats), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true) - let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: statsControllerEntries(data: data, presentationData: presentationData), style: .blocks, emptyStateItem: nil, crossfadeState: false, animateChanges: false) + let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: statsControllerEntries(data: data, presentationData: presentationData), style: .blocks, emptyStateItem: emptyStateItem, crossfadeState: false, animateChanges: false) return (controllerState, (listState, arguments)) } @@ -345,5 +386,6 @@ public func channelStatsController(context: AccountContext, peer: Peer, cachedPe controller.present(c, in: .window(.root), with: a) } } + return controller } diff --git a/submodules/StatisticsUI/Sources/StatsGraphItem.swift b/submodules/StatisticsUI/Sources/StatsGraphItem.swift index a800baf8e9..d8da3b40d1 100644 --- a/submodules/StatisticsUI/Sources/StatsGraphItem.swift +++ b/submodules/StatisticsUI/Sources/StatsGraphItem.swift @@ -12,15 +12,19 @@ import Charts class StatsGraphItem: ListViewItem, ItemListItem { let presentationData: ItemListPresentationData - let title: String let graph: ChannelStatsGraph + let type: ChartType + let height: CGFloat + let getDetailsData: ((Date, (String?) -> Void) -> Void)? let sectionId: ItemListSectionId let style: ItemListStyle - init(presentationData: ItemListPresentationData, title: String, graph: ChannelStatsGraph, sectionId: ItemListSectionId, style: ItemListStyle) { + init(presentationData: ItemListPresentationData, graph: ChannelStatsGraph, type: ChartType, height: CGFloat = 0.0, getDetailsData: ((Date, (String?) -> Void) -> Void)? = nil, sectionId: ItemListSectionId, style: ItemListStyle) { self.presentationData = presentationData - self.title = title self.graph = graph + self.type = type + self.height = height + self.getDetailsData = getDetailsData self.sectionId = sectionId self.style = style } @@ -117,12 +121,12 @@ class StatsGraphItemNode: ListViewItemNode { case .plain: itemBackgroundColor = item.presentationData.theme.list.plainBackgroundColor itemSeparatorColor = item.presentationData.theme.list.itemPlainSeparatorColor - contentSize = CGSize(width: params.width, height: 320.0) + contentSize = CGSize(width: params.width, height: 350.0 + item.height) insets = itemListNeighborsPlainInsets(neighbors) case .blocks: itemBackgroundColor = item.presentationData.theme.list.itemBlocksBackgroundColor itemSeparatorColor = item.presentationData.theme.list.itemBlocksSeparatorColor - contentSize = CGSize(width: params.width, height: 320.0) + contentSize = CGSize(width: params.width, height: 350.0 + item.height) insets = itemListNeighborsGroupedInsets(neighbors) } @@ -137,11 +141,7 @@ class StatsGraphItemNode: ListViewItemNode { strongSelf.bottomStripeNode.backgroundColor = itemSeparatorColor strongSelf.backgroundNode.backgroundColor = itemBackgroundColor } - - if let updatedGraph = updatedGraph, case let .Loaded(data) = updatedGraph { - strongSelf.chartNode.setup(data) - } - + switch item.style { case .plain: if strongSelf.backgroundNode.supernode != nil { @@ -179,12 +179,20 @@ class StatsGraphItemNode: ListViewItemNode { bottomStripeInset = 0.0 } - strongSelf.chartNode.frame = CGRect(origin: CGPoint(x: leftInset, y: -30.0), size: CGSize(width: layout.size.width - leftInset - rightInset, height: 350.0)) + strongSelf.chartNode.frame = CGRect(origin: CGPoint(x: leftInset, y: 0.0), size: CGSize(width: layout.size.width - leftInset - rightInset, height: 400.0)) strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight))) strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: separatorHeight)) strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - bottomStripeInset, height: separatorHeight)) } + + if let updatedGraph = updatedGraph, case let .Loaded(_, data) = updatedGraph { + strongSelf.chartNode.setup(data, type: item.type, getDetailsData: { [weak self] date, completion in + if let strongSelf = self, let item = strongSelf.item { + item.getDetailsData?(date, completion) + } + }) + } } }) } diff --git a/submodules/StatisticsUI/Sources/StatsOverviewItem.swift b/submodules/StatisticsUI/Sources/StatsOverviewItem.swift index 5e85186e30..fd5c1a5083 100644 --- a/submodules/StatisticsUI/Sources/StatsOverviewItem.swift +++ b/submodules/StatisticsUI/Sources/StatsOverviewItem.swift @@ -151,32 +151,60 @@ class StatsOverviewItemNode: ListViewItemNode { updatedTheme = item.presentationData.theme } - let valueFont = Font.regular(item.presentationData.fontSize.itemListBaseHeaderFontSize) + let valueFont = Font.semibold(item.presentationData.fontSize.itemListBaseFontSize) let titleFont = Font.regular(item.presentationData.fontSize.itemListBaseHeaderFontSize) let deltaFont = Font.regular(item.presentationData.fontSize.itemListBaseHeaderFontSize) - let (followersValueLabelLayout, followersValueLabelApply) = makeFollowersValueLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "221K", font: valueFont, textColor: item.presentationData.theme.list.sectionHeaderTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 100.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + let (followersValueLabelLayout, followersValueLabelApply) = makeFollowersValueLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: compactNumericCountString(Int(item.stats.followers.current)), font: valueFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 140.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + + let (viewsPerPostValueLabelLayout, viewsPerPostValueLabelApply) = makeViewsPerPostValueLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: compactNumericCountString(Int(item.stats.viewsPerPost.current)), font: valueFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 140.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) - let (viewsPerPostValueLabelLayout, viewsPerPostValueLabelApply) = makeViewsPerPostValueLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "120K", font: valueFont, textColor: item.presentationData.theme.list.sectionHeaderTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 100.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + let (sharesPerPostValueLabelLayout, sharesPerPostValueLabelApply) = makeSharesPerPostValueLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: compactNumericCountString(Int(item.stats.sharesPerPost.current)), font: valueFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 100.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) - let (sharesPerPostValueLabelLayout, sharesPerPostValueLabelApply) = makeSharesPerPostValueLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "350", font: valueFont, textColor: item.presentationData.theme.list.sectionHeaderTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 100.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + var enabledNotifications: Double = 0.0 + if item.stats.enabledNotifications.total > 0 { + enabledNotifications = item.stats.enabledNotifications.value / item.stats.enabledNotifications.total + } - let (enabledNotificationsValueLabelLayout, enabledNotificationsValueLabelApply) = makeEnabledNotificationsValueLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "22.77%", font: valueFont, textColor: item.presentationData.theme.list.sectionHeaderTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 100.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + let (enabledNotificationsValueLabelLayout, enabledNotificationsValueLabelApply) = makeEnabledNotificationsValueLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: String(format: "%.02f%%", enabledNotifications * 100.0), font: valueFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 100.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + let (followersTitleLabelLayout, followersTitleLabelApply) = makeFollowersTitleLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.presentationData.strings.Stats_Followers, font: titleFont, textColor: item.presentationData.theme.list.sectionHeaderTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 140.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) - let (followersTitleLabelLayout, followersTitleLabelApply) = makeFollowersTitleLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "Followers", font: valueFont, textColor: item.presentationData.theme.list.sectionHeaderTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 100.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + let (viewsPerPostTitleLabelLayout, viewsPerPostTitleLabelApply) = makeViewsPerPostTitleLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.presentationData.strings.Stats_ViewsPerPost, font: titleFont, textColor: item.presentationData.theme.list.sectionHeaderTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 140.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) - let (viewsPerPostTitleLabelLayout, viewsPerPostTitleLabelApply) = makeViewsPerPostTitleLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "Views Per Post", font: valueFont, textColor: item.presentationData.theme.list.sectionHeaderTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 100.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + let (sharesPerPostTitleLabelLayout, sharesPerPostTitleLabelApply) = makeSharesPerPostTitleLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.presentationData.strings.Stats_SharesPerPost, font: titleFont, textColor: item.presentationData.theme.list.sectionHeaderTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 140.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) - let (sharesPerPostTitleLabelLayout, sharesPerPostTitleLabelApply) = makeSharesPerPostTitleLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "Shares Per Post", font: valueFont, textColor: item.presentationData.theme.list.sectionHeaderTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 100.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + let (enabledNotificationsTitleLabelLayout, enabledNotificationsTitleLabelApply) = makeEnabledNotificationsTitleLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.presentationData.strings.Stats_EnabledNotifications, font: titleFont, textColor: item.presentationData.theme.list.sectionHeaderTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 140.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) - let (enabledNotificationsTitleLabelLayout, enabledNotificationsTitleLabelApply) = makeEnabledNotificationsTitleLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "Enabled Notifications", font: valueFont, textColor: item.presentationData.theme.list.sectionHeaderTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 100.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + let followersDeltaValue = item.stats.followers.current - item.stats.followers.previous + let followersDeltaCompact = compactNumericCountString(abs(Int(followersDeltaValue))) + let followersDelta = followersDeltaValue > 0 ? "+\(followersDeltaCompact)" : "-\(followersDeltaCompact)" + var followersDeltaPercentage = 0.0 + if item.stats.followers.previous > 0.0 { + followersDeltaPercentage = abs(followersDeltaValue / item.stats.followers.previous) + } + + let (followersDeltaLabelLayout, followersDeltaLabelApply) = makeFollowersDeltaLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: String(format: "%@ (%.02f%%)", followersDelta, followersDeltaPercentage * 100.0), font: deltaFont, textColor: followersDeltaValue > 0.0 ? item.presentationData.theme.list.freeTextSuccessColor : item.presentationData.theme.list.freeTextErrorColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 140.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) - let (followersDeltaLabelLayout, followersDeltaLabelApply) = makeFollowersDeltaLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "+474 (0.21%)", font: valueFont, textColor: item.presentationData.theme.list.sectionHeaderTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 100.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + let viewsPerPostDeltaValue = item.stats.viewsPerPost.current - item.stats.viewsPerPost.previous + let viewsPerPostDeltaCompact = compactNumericCountString(abs(Int(viewsPerPostDeltaValue))) + let viewsPerPostDelta = viewsPerPostDeltaValue > 0 ? "+\(viewsPerPostDeltaCompact)" : "-\(viewsPerPostDeltaCompact)" + var viewsPerPostDeltaPercentage = 0.0 + if item.stats.viewsPerPost.previous > 0.0 { + viewsPerPostDeltaPercentage = abs(viewsPerPostDeltaValue / item.stats.viewsPerPost.previous) + } - let (viewsPerPostDeltaLabelLayout, viewsPerPostDeltaLabelApply) = makeViewsPerPostDeltaLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "-14K (10.68%)", font: valueFont, textColor: item.presentationData.theme.list.sectionHeaderTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 100.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + let (viewsPerPostDeltaLabelLayout, viewsPerPostDeltaLabelApply) = makeViewsPerPostDeltaLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: String(format: "%@ (%.02f%%)", viewsPerPostDelta, viewsPerPostDeltaPercentage * 100.0), font: deltaFont, textColor: viewsPerPostDeltaValue > 0.0 ? item.presentationData.theme.list.freeTextSuccessColor : item.presentationData.theme.list.freeTextErrorColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 140.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) - let (sharesPerPostDeltaLabelLayout, sharesPerPostDeltaLabelApply) = makeSharesPerPostDeltaLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "-134 (27.68%)", font: valueFont, textColor: item.presentationData.theme.list.sectionHeaderTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 100.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + let sharesPerPostDeltaValue = item.stats.sharesPerPost.current - item.stats.sharesPerPost.previous + let sharesPerPostDeltaCompact = compactNumericCountString(abs(Int(sharesPerPostDeltaValue))) + let sharesPerPostDelta = sharesPerPostDeltaValue > 0 ? "+\(sharesPerPostDeltaCompact)" : "-\(sharesPerPostDeltaCompact)" + var sharesPerPostDeltaPercentage = 0.0 + if item.stats.sharesPerPost.previous > 0.0 { + sharesPerPostDeltaPercentage = abs(sharesPerPostDeltaValue / item.stats.sharesPerPost.previous) + } + + let (sharesPerPostDeltaLabelLayout, sharesPerPostDeltaLabelApply) = makeSharesPerPostDeltaLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: String(format: "%@ (%.02f%%)", sharesPerPostDelta, sharesPerPostDeltaPercentage * 100.0), font: deltaFont, textColor: sharesPerPostDeltaValue > 0.0 ? item.presentationData.theme.list.freeTextSuccessColor : item.presentationData.theme.list.freeTextErrorColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 140.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) let contentSize: CGSize let insets: UIEdgeInsets @@ -265,13 +293,28 @@ class StatsOverviewItemNode: ListViewItemNode { strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - bottomStripeInset, height: separatorHeight)) } - strongSelf.followersValueLabel.frame = CGRect(origin: CGPoint(x: leftInset, y: 7.0), size: followersValueLabelLayout.size) + let horizontalSpacing: CGFloat = 4.0 + let verticalSpacing: CGFloat = 70.0 + let topInset: CGFloat = 14.0 + let sideInset: CGFloat = 16.0 - strongSelf.viewsPerPostValueLabel.frame = CGRect(origin: CGPoint(x: leftInset, y: 44.0), size: viewsPerPostValueLabelLayout.size) + strongSelf.followersValueLabel.frame = CGRect(origin: CGPoint(x: sideInset + leftInset, y: topInset), size: followersValueLabelLayout.size) + strongSelf.followersTitleLabel.frame = CGRect(origin: CGPoint(x: sideInset + leftInset, y: strongSelf.followersValueLabel.frame.maxY), size: followersTitleLabelLayout.size) + strongSelf.followersDeltaLabel.frame = CGRect(origin: CGPoint(x: strongSelf.followersValueLabel.frame.maxX + horizontalSpacing, y: strongSelf.followersValueLabel.frame.minY + 4.0), size: followersDeltaLabelLayout.size) - strongSelf.sharesPerPostValueLabel.frame = CGRect(origin: CGPoint(x: layout.size.width / 2.0, y: 44.0), size: sharesPerPostValueLabelLayout.size) + strongSelf.viewsPerPostValueLabel.frame = CGRect(origin: CGPoint(x: sideInset + leftInset, y: verticalSpacing), size: viewsPerPostValueLabelLayout.size) + strongSelf.viewsPerPostTitleLabel.frame = CGRect(origin: CGPoint(x: sideInset + leftInset, y: strongSelf.viewsPerPostValueLabel.frame.maxY), size: viewsPerPostTitleLabelLayout.size) + strongSelf.viewsPerPostDeltaLabel.frame = CGRect(origin: CGPoint(x: strongSelf.viewsPerPostValueLabel.frame.maxX + horizontalSpacing, y: strongSelf.viewsPerPostValueLabel.frame.minY + 4.0), size: viewsPerPostDeltaLabelLayout.size) - strongSelf.enabledNotificationsValueLabel.frame = CGRect(origin: CGPoint(x: layout.size.width / 2.0, y: 7.0), size: enabledNotificationsValueLabelLayout.size) + let rightColumnX = max(layout.size.width / 2.0, max(strongSelf.followersDeltaLabel.frame.maxX, strongSelf.viewsPerPostDeltaLabel.frame.maxX) + horizontalSpacing) + + strongSelf.sharesPerPostValueLabel.frame = CGRect(origin: CGPoint(x: rightColumnX, y: verticalSpacing), size: sharesPerPostValueLabelLayout.size) + strongSelf.enabledNotificationsValueLabel.frame = CGRect(origin: CGPoint(x: rightColumnX, y: topInset), size: enabledNotificationsValueLabelLayout.size) + + strongSelf.sharesPerPostTitleLabel.frame = CGRect(origin: CGPoint(x: rightColumnX, y: strongSelf.sharesPerPostValueLabel.frame.maxY), size: sharesPerPostTitleLabelLayout.size) + strongSelf.enabledNotificationsTitleLabel.frame = CGRect(origin: CGPoint(x: rightColumnX, y: strongSelf.enabledNotificationsValueLabel.frame.maxY), size: enabledNotificationsTitleLabelLayout.size) + + strongSelf.sharesPerPostDeltaLabel.frame = CGRect(origin: CGPoint(x: strongSelf.sharesPerPostValueLabel.frame.maxX + horizontalSpacing, y: strongSelf.sharesPerPostValueLabel.frame.minY + 4.0), size: sharesPerPostDeltaLabelLayout.size) } }) } diff --git a/submodules/SyncCore/Sources/TelegramMediaDice.swift b/submodules/SyncCore/Sources/TelegramMediaDice.swift new file mode 100644 index 0000000000..449c2b9c0e --- /dev/null +++ b/submodules/SyncCore/Sources/TelegramMediaDice.swift @@ -0,0 +1,38 @@ +import Postbox + +public final class TelegramMediaDice: Media { + public let value: Int32? + + public let id: MediaId? = nil + public let peerIds: [PeerId] = [] + + public init(value: Int32? = nil) { + self.value = value + } + + public init(decoder: PostboxDecoder) { + self.value = decoder.decodeOptionalInt32ForKey("v") + } + + public func encode(_ encoder: PostboxEncoder) { + if let value = self.value { + encoder.encodeInt32(value, forKey: "v") + } else { + encoder.encodeNil(forKey: "v") + } + } + + public func isEqual(to other: Media) -> Bool { + if let other = other as? TelegramMediaDice { + if self.value != other.value { + return false + } + return true + } + return false + } + + public func isSemanticallyEqual(to other: Media) -> Bool { + return self.isEqual(to: other) + } +} diff --git a/submodules/TelegramApi/Sources/Api0.swift b/submodules/TelegramApi/Sources/Api0.swift index 8e0cbac997..26baa33fef 100644 --- a/submodules/TelegramApi/Sources/Api0.swift +++ b/submodules/TelegramApi/Sources/Api0.swift @@ -418,7 +418,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-1269012015] = { return Api.messages.AffectedHistory.parse_affectedHistory($0) } dict[1244130093] = { return Api.StatsGraph.parse_statsGraphAsync($0) } dict[-1092839390] = { return Api.StatsGraph.parse_statsGraphError($0) } - dict[-1057809608] = { return Api.StatsGraph.parse_statsGraph($0) } + dict[-1901828938] = { return Api.StatsGraph.parse_statsGraph($0) } dict[-1036572727] = { return Api.account.PasswordInputSettings.parse_passwordInputSettings($0) } dict[878078826] = { return Api.PageTableCell.parse_pageTableCell($0) } dict[-1626209256] = { return Api.ChatBannedRights.parse_chatBannedRights($0) } @@ -529,7 +529,8 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-1837345356] = { return Api.InputChatPhoto.parse_inputChatUploadedPhoto($0) } dict[-1991004873] = { return Api.InputChatPhoto.parse_inputChatPhoto($0) } dict[-368917890] = { return Api.PaymentCharge.parse_paymentCharge($0) } - dict[205195937] = { return Api.stats.BroadcastStats.parse_broadcastStats($0) } + dict[-1387279939] = { return Api.MessageInteractionCounters.parse_messageInteractionCounters($0) } + dict[821185690] = { return Api.stats.BroadcastStats.parse_broadcastStats($0) } dict[-484987010] = { return Api.Updates.parse_updatesTooLong($0) } dict[-1857044719] = { return Api.Updates.parse_updateShortMessage($0) } dict[377562760] = { return Api.Updates.parse_updateShortChatMessage($0) } @@ -775,7 +776,6 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[1041346555] = { return Api.updates.ChannelDifference.parse_channelDifferenceEmpty($0) } dict[543450958] = { return Api.updates.ChannelDifference.parse_channelDifference($0) } dict[-1531132162] = { return Api.updates.ChannelDifference.parse_channelDifferenceTooLong($0) } - dict[-581804346] = { return Api.StatsRowAbsValueAndPrev.parse_statsRowAbsValueAndPrev($0) } dict[-309659827] = { return Api.channels.AdminLogResults.parse_adminLogResults($0) } dict[-264117680] = { return Api.ChatOnlines.parse_chatOnlines($0) } dict[488313413] = { return Api.InputAppEvent.parse_inputAppEvent($0) } @@ -1225,6 +1225,8 @@ public struct Api { _1.serialize(buffer, boxed) case let _1 as Api.PaymentCharge: _1.serialize(buffer, boxed) + case let _1 as Api.MessageInteractionCounters: + _1.serialize(buffer, boxed) case let _1 as Api.stats.BroadcastStats: _1.serialize(buffer, boxed) case let _1 as Api.Updates: @@ -1413,8 +1415,6 @@ public struct Api { _1.serialize(buffer, boxed) case let _1 as Api.updates.ChannelDifference: _1.serialize(buffer, boxed) - case let _1 as Api.StatsRowAbsValueAndPrev: - _1.serialize(buffer, boxed) case let _1 as Api.channels.AdminLogResults: _1.serialize(buffer, boxed) case let _1 as Api.ChatOnlines: diff --git a/submodules/TelegramApi/Sources/Api1.swift b/submodules/TelegramApi/Sources/Api1.swift index 1ce0613a23..1046f9f6f9 100644 --- a/submodules/TelegramApi/Sources/Api1.swift +++ b/submodules/TelegramApi/Sources/Api1.swift @@ -12044,7 +12044,7 @@ public extension Api { public enum StatsGraph: TypeConstructorDescription { case statsGraphAsync(token: String) case statsGraphError(error: String) - case statsGraph(json: Api.DataJSON) + case statsGraph(flags: Int32, json: Api.DataJSON, zoomToken: String?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { @@ -12060,11 +12060,13 @@ public extension Api { } serializeString(error, buffer: buffer, boxed: false) break - case .statsGraph(let json): + case .statsGraph(let flags, let json, let zoomToken): if boxed { - buffer.appendInt32(-1057809608) + buffer.appendInt32(-1901828938) } + serializeInt32(flags, buffer: buffer, boxed: false) json.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 {serializeString(zoomToken!, buffer: buffer, boxed: false)} break } } @@ -12075,8 +12077,8 @@ public extension Api { return ("statsGraphAsync", [("token", token)]) case .statsGraphError(let error): return ("statsGraphError", [("error", error)]) - case .statsGraph(let json): - return ("statsGraph", [("json", json)]) + case .statsGraph(let flags, let json, let zoomToken): + return ("statsGraph", [("flags", flags), ("json", json), ("zoomToken", zoomToken)]) } } @@ -12103,13 +12105,19 @@ public extension Api { } } public static func parse_statsGraph(_ reader: BufferReader) -> StatsGraph? { - var _1: Api.DataJSON? + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.DataJSON? if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.DataJSON + _2 = Api.parse(reader, signature: signature) as? Api.DataJSON } + var _3: String? + if Int(_1!) & Int(1 << 0) != 0 {_3 = parseString(reader) } let _c1 = _1 != nil - if _c1 { - return Api.StatsGraph.statsGraph(json: _1!) + let _c2 = _2 != nil + let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil + if _c1 && _c2 && _c3 { + return Api.StatsGraph.statsGraph(flags: _1!, json: _2!, zoomToken: _3) } else { return nil @@ -14978,6 +14986,48 @@ public extension Api { } } + } + public enum MessageInteractionCounters: TypeConstructorDescription { + case messageInteractionCounters(msgId: Int32, views: Int32, forwards: Int32) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .messageInteractionCounters(let msgId, let views, let forwards): + if boxed { + buffer.appendInt32(-1387279939) + } + serializeInt32(msgId, buffer: buffer, boxed: false) + serializeInt32(views, buffer: buffer, boxed: false) + serializeInt32(forwards, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .messageInteractionCounters(let msgId, let views, let forwards): + return ("messageInteractionCounters", [("msgId", msgId), ("views", views), ("forwards", forwards)]) + } + } + + public static func parse_messageInteractionCounters(_ reader: BufferReader) -> MessageInteractionCounters? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.MessageInteractionCounters.messageInteractionCounters(msgId: _1!, views: _2!, forwards: _3!) + } + else { + return nil + } + } + } public enum Updates: TypeConstructorDescription { case updatesTooLong @@ -21264,54 +21314,6 @@ public extension Api { } } - } - public enum StatsRowAbsValueAndPrev: TypeConstructorDescription { - case statsRowAbsValueAndPrev(id: String, title: String, shortTitle: String, values: Api.StatsAbsValueAndPrev) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .statsRowAbsValueAndPrev(let id, let title, let shortTitle, let values): - if boxed { - buffer.appendInt32(-581804346) - } - serializeString(id, buffer: buffer, boxed: false) - serializeString(title, buffer: buffer, boxed: false) - serializeString(shortTitle, buffer: buffer, boxed: false) - values.serialize(buffer, true) - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .statsRowAbsValueAndPrev(let id, let title, let shortTitle, let values): - return ("statsRowAbsValueAndPrev", [("id", id), ("title", title), ("shortTitle", shortTitle), ("values", values)]) - } - } - - public static func parse_statsRowAbsValueAndPrev(_ reader: BufferReader) -> StatsRowAbsValueAndPrev? { - var _1: String? - _1 = parseString(reader) - var _2: String? - _2 = parseString(reader) - var _3: String? - _3 = parseString(reader) - var _4: Api.StatsAbsValueAndPrev? - if let signature = reader.readInt32() { - _4 = Api.parse(reader, signature: signature) as? Api.StatsAbsValueAndPrev - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.StatsRowAbsValueAndPrev.statsRowAbsValueAndPrev(id: _1!, title: _2!, shortTitle: _3!, values: _4!) - } - else { - return nil - } - } - } public enum ChatOnlines: TypeConstructorDescription { case chatOnlines(onlines: Int32) diff --git a/submodules/TelegramApi/Sources/Api2.swift b/submodules/TelegramApi/Sources/Api2.swift index c4a1ac4fc5..791eb4877a 100644 --- a/submodules/TelegramApi/Sources/Api2.swift +++ b/submodules/TelegramApi/Sources/Api2.swift @@ -539,47 +539,40 @@ public struct payments { public extension Api { public struct stats { public enum BroadcastStats: TypeConstructorDescription { - case broadcastStats(period: Api.StatsDateRangeDays, followers: Api.StatsAbsValueAndPrev, viewsPerPost: Api.StatsAbsValueAndPrev, sharesPerPost: Api.StatsAbsValueAndPrev, enabledNotifications: Api.StatsPercentValue, viewsBySource: [Api.StatsRowAbsValueAndPrev], newFollowersBySource: [Api.StatsRowAbsValueAndPrev], languages: [Api.StatsRowAbsValueAndPrev], growthGraph: Api.StatsGraph, followersGraph: Api.StatsGraph, muteGraph: Api.StatsGraph, topHoursGraph: Api.StatsGraph, interactionsGraph: Api.StatsGraph) + case broadcastStats(period: Api.StatsDateRangeDays, followers: Api.StatsAbsValueAndPrev, viewsPerPost: Api.StatsAbsValueAndPrev, sharesPerPost: Api.StatsAbsValueAndPrev, enabledNotifications: Api.StatsPercentValue, growthGraph: Api.StatsGraph, followersGraph: Api.StatsGraph, muteGraph: Api.StatsGraph, topHoursGraph: Api.StatsGraph, interactionsGraph: Api.StatsGraph, viewsBySourceGraph: Api.StatsGraph, newFollowersBySourceGraph: Api.StatsGraph, languagesGraph: Api.StatsGraph, recentMessageInteractions: [Api.MessageInteractionCounters]) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .broadcastStats(let period, let followers, let viewsPerPost, let sharesPerPost, let enabledNotifications, let viewsBySource, let newFollowersBySource, let languages, let growthGraph, let followersGraph, let muteGraph, let topHoursGraph, let interactionsGraph): + case .broadcastStats(let period, let followers, let viewsPerPost, let sharesPerPost, let enabledNotifications, let growthGraph, let followersGraph, let muteGraph, let topHoursGraph, let interactionsGraph, let viewsBySourceGraph, let newFollowersBySourceGraph, let languagesGraph, let recentMessageInteractions): if boxed { - buffer.appendInt32(205195937) + buffer.appendInt32(821185690) } period.serialize(buffer, true) followers.serialize(buffer, true) viewsPerPost.serialize(buffer, true) sharesPerPost.serialize(buffer, true) enabledNotifications.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(viewsBySource.count)) - for item in viewsBySource { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(newFollowersBySource.count)) - for item in newFollowersBySource { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(languages.count)) - for item in languages { - item.serialize(buffer, true) - } growthGraph.serialize(buffer, true) followersGraph.serialize(buffer, true) muteGraph.serialize(buffer, true) topHoursGraph.serialize(buffer, true) interactionsGraph.serialize(buffer, true) + viewsBySourceGraph.serialize(buffer, true) + newFollowersBySourceGraph.serialize(buffer, true) + languagesGraph.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(recentMessageInteractions.count)) + for item in recentMessageInteractions { + item.serialize(buffer, true) + } break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .broadcastStats(let period, let followers, let viewsPerPost, let sharesPerPost, let enabledNotifications, let viewsBySource, let newFollowersBySource, let languages, let growthGraph, let followersGraph, let muteGraph, let topHoursGraph, let interactionsGraph): - return ("broadcastStats", [("period", period), ("followers", followers), ("viewsPerPost", viewsPerPost), ("sharesPerPost", sharesPerPost), ("enabledNotifications", enabledNotifications), ("viewsBySource", viewsBySource), ("newFollowersBySource", newFollowersBySource), ("languages", languages), ("growthGraph", growthGraph), ("followersGraph", followersGraph), ("muteGraph", muteGraph), ("topHoursGraph", topHoursGraph), ("interactionsGraph", interactionsGraph)]) + case .broadcastStats(let period, let followers, let viewsPerPost, let sharesPerPost, let enabledNotifications, let growthGraph, let followersGraph, let muteGraph, let topHoursGraph, let interactionsGraph, let viewsBySourceGraph, let newFollowersBySourceGraph, let languagesGraph, let recentMessageInteractions): + return ("broadcastStats", [("period", period), ("followers", followers), ("viewsPerPost", viewsPerPost), ("sharesPerPost", sharesPerPost), ("enabledNotifications", enabledNotifications), ("growthGraph", growthGraph), ("followersGraph", followersGraph), ("muteGraph", muteGraph), ("topHoursGraph", topHoursGraph), ("interactionsGraph", interactionsGraph), ("viewsBySourceGraph", viewsBySourceGraph), ("newFollowersBySourceGraph", newFollowersBySourceGraph), ("languagesGraph", languagesGraph), ("recentMessageInteractions", recentMessageInteractions)]) } } @@ -604,17 +597,17 @@ public struct stats { if let signature = reader.readInt32() { _5 = Api.parse(reader, signature: signature) as? Api.StatsPercentValue } - var _6: [Api.StatsRowAbsValueAndPrev]? - if let _ = reader.readInt32() { - _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StatsRowAbsValueAndPrev.self) + var _6: Api.StatsGraph? + if let signature = reader.readInt32() { + _6 = Api.parse(reader, signature: signature) as? Api.StatsGraph } - var _7: [Api.StatsRowAbsValueAndPrev]? - if let _ = reader.readInt32() { - _7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StatsRowAbsValueAndPrev.self) + var _7: Api.StatsGraph? + if let signature = reader.readInt32() { + _7 = Api.parse(reader, signature: signature) as? Api.StatsGraph } - var _8: [Api.StatsRowAbsValueAndPrev]? - if let _ = reader.readInt32() { - _8 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StatsRowAbsValueAndPrev.self) + var _8: Api.StatsGraph? + if let signature = reader.readInt32() { + _8 = Api.parse(reader, signature: signature) as? Api.StatsGraph } var _9: Api.StatsGraph? if let signature = reader.readInt32() { @@ -636,6 +629,10 @@ public struct stats { if let signature = reader.readInt32() { _13 = Api.parse(reader, signature: signature) as? Api.StatsGraph } + var _14: [Api.MessageInteractionCounters]? + if let _ = reader.readInt32() { + _14 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageInteractionCounters.self) + } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil @@ -649,8 +646,9 @@ public struct stats { let _c11 = _11 != nil let _c12 = _12 != nil let _c13 = _13 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 { - return Api.stats.BroadcastStats.broadcastStats(period: _1!, followers: _2!, viewsPerPost: _3!, sharesPerPost: _4!, enabledNotifications: _5!, viewsBySource: _6!, newFollowersBySource: _7!, languages: _8!, growthGraph: _9!, followersGraph: _10!, muteGraph: _11!, topHoursGraph: _12!, interactionsGraph: _13!) + let _c14 = _14 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 { + return Api.stats.BroadcastStats.broadcastStats(period: _1!, followers: _2!, viewsPerPost: _3!, sharesPerPost: _4!, enabledNotifications: _5!, growthGraph: _6!, followersGraph: _7!, muteGraph: _8!, topHoursGraph: _9!, interactionsGraph: _10!, viewsBySourceGraph: _11!, newFollowersBySourceGraph: _12!, languagesGraph: _13!, recentMessageInteractions: _14!) } else { return nil diff --git a/submodules/TelegramApi/Sources/Api3.swift b/submodules/TelegramApi/Sources/Api3.swift index 106c437be6..6d8b391c5a 100644 --- a/submodules/TelegramApi/Sources/Api3.swift +++ b/submodules/TelegramApi/Sources/Api3.swift @@ -3964,11 +3964,13 @@ public extension Api { }) } - public static func loadAsyncGraph(token: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + public static func loadAsyncGraph(flags: Int32, token: String, x: Int64?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() - buffer.appendInt32(1749505346) + buffer.appendInt32(1646092192) + serializeInt32(flags, buffer: buffer, boxed: false) serializeString(token, buffer: buffer, boxed: false) - return (FunctionDescription(name: "stats.loadAsyncGraph", parameters: [("token", token)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.StatsGraph? in + if Int(flags) & Int(1 << 0) != 0 {serializeInt64(x!, buffer: buffer, boxed: false)} + return (FunctionDescription(name: "stats.loadAsyncGraph", parameters: [("flags", flags), ("token", token), ("x", x)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.StatsGraph? in let reader = BufferReader(buffer) var result: Api.StatsGraph? if let signature = reader.readInt32() { diff --git a/submodules/TelegramBaseController/Sources/TelegramBaseController.swift b/submodules/TelegramBaseController/Sources/TelegramBaseController.swift index 6bd0c6ca87..6aab136dce 100644 --- a/submodules/TelegramBaseController/Sources/TelegramBaseController.swift +++ b/submodules/TelegramBaseController/Sources/TelegramBaseController.swift @@ -97,7 +97,7 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder { return super.visualNavigationInsetHeight + self.additionalHeight } - private var additionalHeight: CGFloat { + public var additionalHeight: CGFloat { var height: CGFloat = 0.0 if let _ = self.mediaAccessoryPanel { height += MediaNavigationAccessoryHeaderNode.minimizedHeight diff --git a/submodules/TelegramCore/Sources/AccountManager.swift b/submodules/TelegramCore/Sources/AccountManager.swift index 0083732259..855e3aa9e2 100644 --- a/submodules/TelegramCore/Sources/AccountManager.swift +++ b/submodules/TelegramCore/Sources/AccountManager.swift @@ -154,6 +154,7 @@ private var declaredEncodables: Void = { declareEncodable(CachedPollOptionResult.self, f: { CachedPollOptionResult(decoder: $0) }) declareEncodable(ChatListFiltersState.self, f: { ChatListFiltersState(decoder: $0) }) declareEncodable(PeersNearbyState.self, f: { PeersNearbyState(decoder: $0) }) + declareEncodable(TelegramMediaDice.self, f: { TelegramMediaDice(decoder: $0) }) return }() diff --git a/submodules/TelegramCore/Sources/AccountViewTracker.swift b/submodules/TelegramCore/Sources/AccountViewTracker.swift index 77d5c28d65..670acb03fc 100644 --- a/submodules/TelegramCore/Sources/AccountViewTracker.swift +++ b/submodules/TelegramCore/Sources/AccountViewTracker.swift @@ -1330,7 +1330,7 @@ public final class AccountViewTracker { }) } - public func tailChatListView(groupId: PeerGroupId, filterPredicate: ((Peer, PeerNotificationSettings?, Bool) -> Bool)? = nil, count: Int) -> Signal<(ChatListView, ViewUpdateType), NoError> { + public func tailChatListView(groupId: PeerGroupId, filterPredicate: ChatListFilterPredicate? = nil, count: Int) -> Signal<(ChatListView, ViewUpdateType), NoError> { if let account = self.account { return self.wrappedChatListView(signal: account.postbox.tailChatListView(groupId: groupId, filterPredicate: filterPredicate, count: count, summaryComponents: ChatListEntrySummaryComponents(tagSummary: ChatListEntryMessageTagSummaryComponent(tag: .unseenPersonalMessage, namespace: Namespaces.Message.Cloud), actionsSummary: ChatListEntryPendingMessageActionsSummaryComponent(type: PendingMessageActionType.consumeUnseenPersonalMessage, namespace: Namespaces.Message.Cloud)))) } else { @@ -1338,7 +1338,7 @@ public final class AccountViewTracker { } } - public func aroundChatListView(groupId: PeerGroupId, filterPredicate: ((Peer, PeerNotificationSettings?, Bool) -> Bool)? = nil, index: ChatListIndex, count: Int) -> Signal<(ChatListView, ViewUpdateType), NoError> { + public func aroundChatListView(groupId: PeerGroupId, filterPredicate: ChatListFilterPredicate? = nil, index: ChatListIndex, count: Int) -> Signal<(ChatListView, ViewUpdateType), NoError> { if let account = self.account { return self.wrappedChatListView(signal: account.postbox.aroundChatListView(groupId: groupId, filterPredicate: filterPredicate, index: index, count: count, summaryComponents: ChatListEntrySummaryComponents(tagSummary: ChatListEntryMessageTagSummaryComponent(tag: .unseenPersonalMessage, namespace: Namespaces.Message.Cloud), actionsSummary: ChatListEntryPendingMessageActionsSummaryComponent(type: PendingMessageActionType.consumeUnseenPersonalMessage, namespace: Namespaces.Message.Cloud)))) } else { diff --git a/submodules/TelegramCore/Sources/ApplyMaxReadIndexInteractively.swift b/submodules/TelegramCore/Sources/ApplyMaxReadIndexInteractively.swift index c75b823b79..a322c58cc6 100644 --- a/submodules/TelegramCore/Sources/ApplyMaxReadIndexInteractively.swift +++ b/submodules/TelegramCore/Sources/ApplyMaxReadIndexInteractively.swift @@ -161,8 +161,8 @@ public func clearPeerUnseenPersonalMessagesInteractively(account: Account, peerI |> ignoreValues } -public func markAllChatsAsReadInteractively(transaction: Transaction, viewTracker: AccountViewTracker, groupId: PeerGroupId) { - for peerId in transaction.getUnreadChatListPeerIds(groupId: groupId) { +public func markAllChatsAsReadInteractively(transaction: Transaction, viewTracker: AccountViewTracker, groupId: PeerGroupId, filterPredicate: ChatListFilterPredicate?) { + for peerId in transaction.getUnreadChatListPeerIds(groupId: groupId, filterPredicate: filterPredicate) { togglePeerUnreadMarkInteractively(transaction: transaction, viewTracker: viewTracker, peerId: peerId, setToValue: false) } } diff --git a/submodules/TelegramCore/Sources/ChannelStatistics.swift b/submodules/TelegramCore/Sources/ChannelStatistics.swift index ff2a71fdfd..18b49c67de 100644 --- a/submodules/TelegramCore/Sources/ChannelStatistics.swift +++ b/submodules/TelegramCore/Sources/ChannelStatistics.swift @@ -16,21 +16,25 @@ public struct ChannelStatsValue: Equatable { } public struct ChannelStatsPercentValue: Equatable { - public let fraction: Double + public let value: Double public let total: Double } -public struct ChannelStatsNamedValue: Equatable { - public let id: String - public let title: String - public let shortTitle: String - public let value: ChannelStatsValue -} - public enum ChannelStatsGraph: Equatable { case OnDemand(token: String) case Failed(error: String) - case Loaded(data: String) + case Loaded(token: String?, data: String) + + var token: String? { + switch self { + case let .OnDemand(token): + return token + case let .Loaded(token, data): + return token + default: + return nil + } + } } public final class ChannelStats: Equatable { @@ -39,29 +43,29 @@ public final class ChannelStats: Equatable { public let viewsPerPost: ChannelStatsValue public let sharesPerPost: ChannelStatsValue public let enabledNotifications: ChannelStatsPercentValue - public let viewsBySource: [ChannelStatsNamedValue] - public let newFollowersBySource: [ChannelStatsNamedValue] - public let languages: [ChannelStatsNamedValue] public let growthGraph: ChannelStatsGraph public let followersGraph: ChannelStatsGraph public let muteGraph: ChannelStatsGraph public let topHoursGraph: ChannelStatsGraph public let interactionsGraph: ChannelStatsGraph - - public init(period: ChannelStatsDateRange, followers: ChannelStatsValue, viewsPerPost: ChannelStatsValue, sharesPerPost: ChannelStatsValue, enabledNotifications: ChannelStatsPercentValue, viewsBySource: [ChannelStatsNamedValue], newFollowersBySource: [ChannelStatsNamedValue], languages: [ChannelStatsNamedValue], growthGraph: ChannelStatsGraph, followersGraph: ChannelStatsGraph, muteGraph: ChannelStatsGraph, topHoursGraph: ChannelStatsGraph, interactionsGraph: ChannelStatsGraph) { + public let viewsBySourceGraph: ChannelStatsGraph + public let newFollowersBySourceGraph: ChannelStatsGraph + public let languagesGraph: ChannelStatsGraph + + public init(period: ChannelStatsDateRange, followers: ChannelStatsValue, viewsPerPost: ChannelStatsValue, sharesPerPost: ChannelStatsValue, enabledNotifications: ChannelStatsPercentValue, growthGraph: ChannelStatsGraph, followersGraph: ChannelStatsGraph, muteGraph: ChannelStatsGraph, topHoursGraph: ChannelStatsGraph, interactionsGraph: ChannelStatsGraph, viewsBySourceGraph: ChannelStatsGraph, newFollowersBySourceGraph: ChannelStatsGraph, languagesGraph: ChannelStatsGraph) { self.period = period self.followers = followers self.viewsPerPost = viewsPerPost self.sharesPerPost = sharesPerPost self.enabledNotifications = enabledNotifications - self.viewsBySource = viewsBySource - self.newFollowersBySource = newFollowersBySource - self.languages = languages self.growthGraph = growthGraph self.followersGraph = followersGraph self.muteGraph = muteGraph self.topHoursGraph = topHoursGraph self.interactionsGraph = interactionsGraph + self.viewsBySourceGraph = viewsBySourceGraph + self.newFollowersBySourceGraph = newFollowersBySourceGraph + self.languagesGraph = languagesGraph } public static func == (lhs: ChannelStats, rhs: ChannelStats) -> Bool { @@ -80,15 +84,6 @@ public final class ChannelStats: Equatable { if lhs.enabledNotifications != rhs.enabledNotifications { return false } - if lhs.viewsBySource != rhs.viewsBySource { - return false - } - if lhs.newFollowersBySource != rhs.newFollowersBySource { - return false - } - if lhs.languages != rhs.languages { - return false - } if lhs.growthGraph != rhs.growthGraph { return false } @@ -104,27 +99,48 @@ public final class ChannelStats: Equatable { if lhs.interactionsGraph != rhs.interactionsGraph { return false } + if lhs.viewsBySourceGraph != rhs.viewsBySourceGraph { + return false + } + if lhs.newFollowersBySourceGraph != rhs.newFollowersBySourceGraph { + return false + } + if lhs.languagesGraph != rhs.languagesGraph { + return false + } return true } public func withUpdatedGrowthGraph(_ growthGraph: ChannelStatsGraph) -> ChannelStats { - return ChannelStats(period: self.period, followers: self.followers, viewsPerPost: self.viewsPerPost, sharesPerPost: self.sharesPerPost, enabledNotifications: self.enabledNotifications, viewsBySource: self.viewsBySource, newFollowersBySource: self.newFollowersBySource, languages: self.languages, growthGraph: growthGraph, followersGraph: self.followersGraph, muteGraph: self.muteGraph, topHoursGraph: self.topHoursGraph, interactionsGraph: self.interactionsGraph) + return ChannelStats(period: self.period, followers: self.followers, viewsPerPost: self.viewsPerPost, sharesPerPost: self.sharesPerPost, enabledNotifications: self.enabledNotifications, growthGraph: growthGraph, followersGraph: self.followersGraph, muteGraph: self.muteGraph, topHoursGraph: self.topHoursGraph, interactionsGraph: self.interactionsGraph, viewsBySourceGraph: self.viewsBySourceGraph, newFollowersBySourceGraph: self.newFollowersBySourceGraph, languagesGraph: self.languagesGraph) } public func withUpdatedFollowersGraph(_ followersGraph: ChannelStatsGraph) -> ChannelStats { - return ChannelStats(period: self.period, followers: self.followers, viewsPerPost: self.viewsPerPost, sharesPerPost: self.sharesPerPost, enabledNotifications: self.enabledNotifications, viewsBySource: self.viewsBySource, newFollowersBySource: self.newFollowersBySource, languages: self.languages, growthGraph: self.growthGraph, followersGraph: followersGraph, muteGraph: self.muteGraph, topHoursGraph: self.topHoursGraph, interactionsGraph: self.interactionsGraph) + return ChannelStats(period: self.period, followers: self.followers, viewsPerPost: self.viewsPerPost, sharesPerPost: self.sharesPerPost, enabledNotifications: self.enabledNotifications, growthGraph: self.growthGraph, followersGraph: followersGraph, muteGraph: self.muteGraph, topHoursGraph: self.topHoursGraph, interactionsGraph: self.interactionsGraph, viewsBySourceGraph: self.viewsBySourceGraph, newFollowersBySourceGraph: self.newFollowersBySourceGraph, languagesGraph: self.languagesGraph) } public func withUpdatedMuteGraph(_ muteGraph: ChannelStatsGraph) -> ChannelStats { - return ChannelStats(period: self.period, followers: self.followers, viewsPerPost: self.viewsPerPost, sharesPerPost: self.sharesPerPost, enabledNotifications: self.enabledNotifications, viewsBySource: self.viewsBySource, newFollowersBySource: self.newFollowersBySource, languages: self.languages, growthGraph: self.growthGraph, followersGraph: self.followersGraph, muteGraph: muteGraph, topHoursGraph: self.topHoursGraph, interactionsGraph: self.interactionsGraph) + return ChannelStats(period: self.period, followers: self.followers, viewsPerPost: self.viewsPerPost, sharesPerPost: self.sharesPerPost, enabledNotifications: self.enabledNotifications, growthGraph: self.growthGraph, followersGraph: self.followersGraph, muteGraph: muteGraph, topHoursGraph: self.topHoursGraph, interactionsGraph: self.interactionsGraph, viewsBySourceGraph: self.viewsBySourceGraph, newFollowersBySourceGraph: self.newFollowersBySourceGraph, languagesGraph: self.languagesGraph) } public func withUpdatedTopHoursGraph(_ viewsByHourGraph: ChannelStatsGraph) -> ChannelStats { - return ChannelStats(period: self.period, followers: self.followers, viewsPerPost: self.viewsPerPost, sharesPerPost: self.sharesPerPost, enabledNotifications: self.enabledNotifications, viewsBySource: self.viewsBySource, newFollowersBySource: self.newFollowersBySource, languages: self.languages, growthGraph: self.growthGraph, followersGraph: self.followersGraph, muteGraph: self.muteGraph, topHoursGraph: viewsByHourGraph, interactionsGraph: self.interactionsGraph) + return ChannelStats(period: self.period, followers: self.followers, viewsPerPost: self.viewsPerPost, sharesPerPost: self.sharesPerPost, enabledNotifications: self.enabledNotifications, growthGraph: self.growthGraph, followersGraph: self.followersGraph, muteGraph: self.muteGraph, topHoursGraph: viewsByHourGraph, interactionsGraph: self.interactionsGraph, viewsBySourceGraph: self.viewsBySourceGraph, newFollowersBySourceGraph: self.newFollowersBySourceGraph, languagesGraph: self.languagesGraph) } public func withUpdatedInteractionsGraph(_ interactionsGraph: ChannelStatsGraph) -> ChannelStats { - return ChannelStats(period: self.period, followers: self.followers, viewsPerPost: self.viewsPerPost, sharesPerPost: self.sharesPerPost, enabledNotifications: self.enabledNotifications, viewsBySource: self.viewsBySource, newFollowersBySource: self.newFollowersBySource, languages: self.languages, growthGraph: self.growthGraph, followersGraph: self.followersGraph, muteGraph: self.muteGraph, topHoursGraph: self.topHoursGraph, interactionsGraph: interactionsGraph) + return ChannelStats(period: self.period, followers: self.followers, viewsPerPost: self.viewsPerPost, sharesPerPost: self.sharesPerPost, enabledNotifications: self.enabledNotifications, growthGraph: self.growthGraph, followersGraph: self.followersGraph, muteGraph: self.muteGraph, topHoursGraph: self.topHoursGraph, interactionsGraph: interactionsGraph, viewsBySourceGraph: self.viewsBySourceGraph, newFollowersBySourceGraph: self.newFollowersBySourceGraph, languagesGraph: self.languagesGraph) + } + + public func withUpdatedViewsBySourceGraph(_ viewsBySourceGraph: ChannelStatsGraph) -> ChannelStats { + return ChannelStats(period: self.period, followers: self.followers, viewsPerPost: self.viewsPerPost, sharesPerPost: self.sharesPerPost, enabledNotifications: self.enabledNotifications, growthGraph: self.growthGraph, followersGraph: self.followersGraph, muteGraph: self.muteGraph, topHoursGraph: self.topHoursGraph, interactionsGraph: self.interactionsGraph, viewsBySourceGraph: viewsBySourceGraph, newFollowersBySourceGraph: self.newFollowersBySourceGraph, languagesGraph: self.languagesGraph) + } + + public func withUpdatedNewFollowersBySourceGraph(_ newFollowersBySourceGraph: ChannelStatsGraph) -> ChannelStats { + return ChannelStats(period: self.period, followers: self.followers, viewsPerPost: self.viewsPerPost, sharesPerPost: self.sharesPerPost, enabledNotifications: self.enabledNotifications, growthGraph: self.growthGraph, followersGraph: self.followersGraph, muteGraph: self.muteGraph, topHoursGraph: self.topHoursGraph, interactionsGraph: self.interactionsGraph, viewsBySourceGraph: self.viewsBySourceGraph, newFollowersBySourceGraph: newFollowersBySourceGraph, languagesGraph: self.languagesGraph) + } + + public func withUpdatedLanguagesGraph(_ languagesGraph: ChannelStatsGraph) -> ChannelStats { + return ChannelStats(period: self.period, followers: self.followers, viewsPerPost: self.viewsPerPost, sharesPerPost: self.sharesPerPost, enabledNotifications: self.enabledNotifications, growthGraph: self.growthGraph, followersGraph: self.followersGraph, muteGraph: self.muteGraph, topHoursGraph: self.topHoursGraph, interactionsGraph: self.interactionsGraph, viewsBySourceGraph: self.viewsBySourceGraph, newFollowersBySourceGraph: self.newFollowersBySourceGraph, languagesGraph: languagesGraph) } } @@ -132,48 +148,54 @@ public struct ChannelStatsContextState: Equatable { public var stats: ChannelStats? } -private func requestStats(network: Network, datacenterId: Int32, peer: Peer, dark: Bool = false) -> Signal { - return .never() - /*guard let inputChannel = apiInputChannel(peer) else { - return .never() - } - - var flags: Int32 = 0 - if dark { - flags |= (1 << 1) - } - - let signal: Signal - if network.datacenterId != datacenterId { - signal = network.download(datacenterId: Int(datacenterId), isMedia: false, tag: nil) - |> castError(MTRpcError.self) - |> mapToSignal { worker in - return worker.request(Api.functions.stats.getBroadcastStats(flags: flags, channel: inputChannel)) +private func requestStats(postbox: Postbox, network: Network, datacenterId: Int32, peerId: PeerId, dark: Bool = false) -> Signal { + return postbox.transaction { transaction -> Peer? in + return transaction.getPeer(peerId) + } |> mapToSignal { peer -> Signal in + guard let peer = peer, let inputChannel = apiInputChannel(peer) else { + return .never() + } + + var flags: Int32 = 0 + if dark { + flags |= (1 << 1) + } + + let signal: Signal + if network.datacenterId != datacenterId { + signal = network.download(datacenterId: Int(datacenterId), isMedia: false, tag: nil) + |> castError(MTRpcError.self) + |> mapToSignal { worker in + return worker.request(Api.functions.stats.getBroadcastStats(flags: flags, channel: inputChannel)) + } + } else { + signal = network.request(Api.functions.stats.getBroadcastStats(flags: flags, channel: inputChannel)) + } + + return signal + |> map { result -> ChannelStats? in + return ChannelStats(apiBroadcastStats: result) + } + |> `catch` { _ -> Signal in + return .single(nil) } - } else { - signal = network.request(Api.functions.stats.getBroadcastStats(flags: flags, channel: inputChannel)) } - - return signal - |> map { result -> ChannelStats? in - return ChannelStats(apiBroadcastStats: result) - } - |> `catch` { _ -> Signal in - return .single(nil) - }*/ } -private func requestGraph(network: Network, datacenterId: Int32, token: String) -> Signal { - return .never() - /*let signal: Signal +private func requestGraph(network: Network, datacenterId: Int32, token: String, x: Int64? = nil) -> Signal { + var flags: Int32 = 0 + if let _ = x { + flags |= (1 << 0) + } + let signal: Signal if network.datacenterId != datacenterId { signal = network.download(datacenterId: Int(datacenterId), isMedia: false, tag: nil) |> castError(MTRpcError.self) |> mapToSignal { worker in - return worker.request(Api.functions.stats.loadAsyncGraph(token: token)) + return worker.request(Api.functions.stats.loadAsyncGraph(flags: flags, token: token, x: x)) } } else { - signal = network.request(Api.functions.stats.loadAsyncGraph(token: token)) + signal = network.request(Api.functions.stats.loadAsyncGraph(flags: flags, token: token, x: x)) } return signal @@ -182,13 +204,14 @@ private func requestGraph(network: Network, datacenterId: Int32, token: String) } |> `catch` { _ -> Signal in return .single(nil) - }*/ + } } private final class ChannelStatsContextImpl { + private let postbox: Postbox private let network: Network - private let peer: Peer private let datacenterId: Int32 + private let peerId: PeerId private var _state: ChannelStatsContextState { didSet { @@ -205,12 +228,13 @@ private final class ChannelStatsContextImpl { private let disposable = MetaDisposable() private let disposables = DisposableDict() - init(network: Network, datacenterId: Int32, peer: Peer) { + init(postbox: Postbox, network: Network, datacenterId: Int32, peerId: PeerId) { assert(Queue.mainQueue().isCurrent()) + self.postbox = postbox self.network = network - self.peer = peer self.datacenterId = datacenterId + self.peerId = peerId self._state = ChannelStatsContextState(stats: nil) self._statePromise.set(.single(self._state)) @@ -226,7 +250,7 @@ private final class ChannelStatsContextImpl { private func load() { assert(Queue.mainQueue().isCurrent()) - self.disposable.set((requestStats(network: self.network, datacenterId: self.datacenterId, peer: self.peer) + self.disposable.set((requestStats(postbox: self.postbox, network: self.network, datacenterId: self.datacenterId, peerId: self.peerId) |> deliverOnMainQueue).start(next: { [weak self] stats in if let strongSelf = self { strongSelf._state = ChannelStatsContextState(stats: stats) @@ -309,6 +333,59 @@ private final class ChannelStatsContextImpl { }), forKey: token) } } + + func loadViewsBySourceGraph() { + guard let stats = self._state.stats else { + return + } + if case let .OnDemand(token) = stats.viewsBySourceGraph { + self.disposables.set((requestGraph(network: self.network, datacenterId: self.datacenterId, token: token) + |> deliverOnMainQueue).start(next: { [weak self] graph in + if let strongSelf = self, let graph = graph { + strongSelf._state = ChannelStatsContextState(stats: strongSelf._state.stats?.withUpdatedViewsBySourceGraph(graph)) + strongSelf._statePromise.set(.single(strongSelf._state)) + } + }), forKey: token) + } + } + + func loadNewFollowersBySourceGraph() { + guard let stats = self._state.stats else { + return + } + if case let .OnDemand(token) = stats.newFollowersBySourceGraph { + self.disposables.set((requestGraph(network: self.network, datacenterId: self.datacenterId, token: token) + |> deliverOnMainQueue).start(next: { [weak self] graph in + if let strongSelf = self, let graph = graph { + strongSelf._state = ChannelStatsContextState(stats: strongSelf._state.stats?.withUpdatedNewFollowersBySourceGraph(graph)) + strongSelf._statePromise.set(.single(strongSelf._state)) + } + }), forKey: token) + } + } + + func loadLanguagesGraph() { + guard let stats = self._state.stats else { + return + } + if case let .OnDemand(token) = stats.languagesGraph { + self.disposables.set((requestGraph(network: self.network, datacenterId: self.datacenterId, token: token) + |> deliverOnMainQueue).start(next: { [weak self] graph in + if let strongSelf = self, let graph = graph { + strongSelf._state = ChannelStatsContextState(stats: strongSelf._state.stats?.withUpdatedLanguagesGraph(graph)) + strongSelf._statePromise.set(.single(strongSelf._state)) + } + }), forKey: token) + } + } + + func loadDetailedGraph(_ graph: ChannelStatsGraph, x: Int64) -> Signal { + if let token = graph.token { + return requestGraph(network: self.network, datacenterId: self.datacenterId, token: token, x: x) + } else { + return .single(nil) + } + } } public final class ChannelStatsContext { @@ -326,12 +403,12 @@ public final class ChannelStatsContext { } } - public init(network: Network, datacenterId: Int32, peer: Peer) { + public init(postbox: Postbox, network: Network, datacenterId: Int32, peerId: PeerId) { self.impl = QueueLocalObject(queue: Queue.mainQueue(), generate: { - return ChannelStatsContextImpl(network: network, datacenterId: datacenterId, peer: peer) + return ChannelStatsContextImpl(postbox: postbox, network: network, datacenterId: datacenterId, peerId: peerId) }) } - + public func loadGrowthGraph() { self.impl.with { impl in impl.loadGrowthGraph() @@ -361,14 +438,44 @@ public final class ChannelStatsContext { impl.loadInteractionsGraph() } } + + public func loadViewsBySourceGraph() { + self.impl.with { impl in + impl.loadViewsBySourceGraph() + } + } + + public func loadNewFollowersBySourceGraph() { + self.impl.with { impl in + impl.loadNewFollowersBySourceGraph() + } + } + + public func loadLanguagesGraph() { + self.impl.with { impl in + impl.loadLanguagesGraph() + } + } + + public func loadDetailedGraph(_ graph: ChannelStatsGraph, x: Int64) -> Signal { + return Signal { subscriber in + let disposable = MetaDisposable() + self.impl.with { impl in + disposable.set(impl.loadDetailedGraph(graph, x: x).start(next: { value in + subscriber.putNext(value) + })) + } + return disposable + } + } } -/*extension ChannelStatsGraph { +extension ChannelStatsGraph { init(apiStatsGraph: Api.StatsGraph) { switch apiStatsGraph { - case let .statsGraph(json): + case let .statsGraph(_, json, zoomToken): if case let .dataJSON(string) = json { - self = .Loaded(data: string) + self = .Loaded(token: zoomToken, data: string) } else { self = .Failed(error: "") } @@ -398,20 +505,11 @@ extension ChannelStatsValue { } } -extension ChannelStatsNamedValue { - init(apiStatsRowAbsValueAndPrev: Api.StatsRowAbsValueAndPrev) { - switch apiStatsRowAbsValueAndPrev { - case let .statsRowAbsValueAndPrev(id, title, shortTitle, values): - self = ChannelStatsNamedValue(id: id, title: title, shortTitle: shortTitle, value: ChannelStatsValue(apiStatsAbsValueAndPrev: values)) - } - } -} - extension ChannelStatsPercentValue { init(apiPercentValue: Api.StatsPercentValue) { switch apiPercentValue { case let .statsPercentValue(part, total): - self = ChannelStatsPercentValue(fraction: part, total: total) + self = ChannelStatsPercentValue(value: part, total: total) } } } @@ -419,9 +517,8 @@ extension ChannelStatsPercentValue { extension ChannelStats { convenience init(apiBroadcastStats: Api.stats.BroadcastStats) { switch apiBroadcastStats { - case let .broadcastStats(period, followers, viewsPerPost, sharesPerPost, enabledNotifications, viewsBySource, newFollowersBySource, languages, growthGraph, followersGraph, muteGraph, topHoursGraph, interactionsGraph): - self.init(period: ChannelStatsDateRange(apiStatsDateRangeDays: period), followers: ChannelStatsValue(apiStatsAbsValueAndPrev: followers), viewsPerPost: ChannelStatsValue(apiStatsAbsValueAndPrev: viewsPerPost), sharesPerPost: ChannelStatsValue(apiStatsAbsValueAndPrev: sharesPerPost), enabledNotifications: ChannelStatsPercentValue(apiPercentValue: enabledNotifications), viewsBySource: viewsBySource.map { ChannelStatsNamedValue(apiStatsRowAbsValueAndPrev: $0) }, newFollowersBySource: newFollowersBySource.map { ChannelStatsNamedValue(apiStatsRowAbsValueAndPrev: $0) }, languages: languages.map { ChannelStatsNamedValue(apiStatsRowAbsValueAndPrev: $0) }, growthGraph: ChannelStatsGraph(apiStatsGraph: growthGraph), followersGraph: ChannelStatsGraph(apiStatsGraph: followersGraph), muteGraph: ChannelStatsGraph(apiStatsGraph: muteGraph), topHoursGraph: ChannelStatsGraph(apiStatsGraph: topHoursGraph), interactionsGraph: ChannelStatsGraph(apiStatsGraph: interactionsGraph)) + case let .broadcastStats(period, followers, viewsPerPost, sharesPerPost, enabledNotifications, growthGraph, followersGraph, muteGraph, topHoursGraph, interactionsGraph, viewsBySourceGraph, newFollowersBySourceGraph, languagesGraph, recentMessageInteractions): + self.init(period: ChannelStatsDateRange(apiStatsDateRangeDays: period), followers: ChannelStatsValue(apiStatsAbsValueAndPrev: followers), viewsPerPost: ChannelStatsValue(apiStatsAbsValueAndPrev: viewsPerPost), sharesPerPost: ChannelStatsValue(apiStatsAbsValueAndPrev: sharesPerPost), enabledNotifications: ChannelStatsPercentValue(apiPercentValue: enabledNotifications), growthGraph: ChannelStatsGraph(apiStatsGraph: growthGraph), followersGraph: ChannelStatsGraph(apiStatsGraph: followersGraph), muteGraph: ChannelStatsGraph(apiStatsGraph: muteGraph), topHoursGraph: ChannelStatsGraph(apiStatsGraph: topHoursGraph), interactionsGraph: ChannelStatsGraph(apiStatsGraph: interactionsGraph), viewsBySourceGraph: ChannelStatsGraph(apiStatsGraph: viewsBySourceGraph), newFollowersBySourceGraph: ChannelStatsGraph(apiStatsGraph: newFollowersBySourceGraph), languagesGraph: ChannelStatsGraph(apiStatsGraph: languagesGraph)) } } } -*/ diff --git a/submodules/TelegramCore/Sources/ChatListFiltering.swift b/submodules/TelegramCore/Sources/ChatListFiltering.swift index c5c6822fdd..08c374d900 100644 --- a/submodules/TelegramCore/Sources/ChatListFiltering.swift +++ b/submodules/TelegramCore/Sources/ChatListFiltering.swift @@ -270,8 +270,6 @@ private func requestChatListFilters(postbox: Postbox, network: Network) -> Signa } func managedChatListFilters(postbox: Postbox, network: Network) -> Signal { - return .complete() - return requestChatListFilters(postbox: postbox, network: network) |> `catch` { _ -> Signal<[ChatListFilter], NoError> in return .complete() diff --git a/submodules/TelegramCore/Sources/ManagedChatListHoles.swift b/submodules/TelegramCore/Sources/ManagedChatListHoles.swift index a1388d0fce..dca1df5fad 100644 --- a/submodules/TelegramCore/Sources/ManagedChatListHoles.swift +++ b/submodules/TelegramCore/Sources/ManagedChatListHoles.swift @@ -6,6 +6,7 @@ import SyncCore private final class ManagedChatListHolesState { private var holeDisposables: [ChatListHolesEntry: Disposable] = [:] private var additionalLatestHoleDisposable: (ChatListHole, Disposable)? + private var additionalLatestArchiveHoleDisposable: (ChatListHole, Disposable)? func clearDisposables() -> [Disposable] { let disposables = Array(self.holeDisposables.values) @@ -13,7 +14,7 @@ private final class ManagedChatListHolesState { return disposables } - func update(entries: Set, additionalLatestHole: ChatListHole?) -> (removed: [Disposable], added: [ChatListHolesEntry: MetaDisposable], addedAdditionalLatestHole: (ChatListHole, MetaDisposable)?) { + func update(entries: Set, additionalLatestHole: ChatListHole?, additionalLatestArchiveHole: ChatListHole?) -> (removed: [Disposable], added: [ChatListHolesEntry: MetaDisposable], addedAdditionalLatestHole: (ChatListHole, MetaDisposable)?, addedAdditionalLatestArchiveHole: (ChatListHole, MetaDisposable)?) { var removed: [Disposable] = [] var added: [ChatListHolesEntry: MetaDisposable] = [:] @@ -33,6 +34,7 @@ private final class ManagedChatListHolesState { } var addedAdditionalLatestHole: (ChatListHole, MetaDisposable)? + var addedAdditionalLatestArchiveHole: (ChatListHole, MetaDisposable)? if self.holeDisposables.isEmpty { if self.additionalLatestHoleDisposable?.0 != additionalLatestHole { if let (_, disposable) = self.additionalLatestHoleDisposable { @@ -44,9 +46,22 @@ private final class ManagedChatListHolesState { addedAdditionalLatestHole = (additionalLatestHole, disposable) } } + + if additionalLatestHole == nil { + if self.additionalLatestArchiveHoleDisposable?.0 != additionalLatestArchiveHole { + if let (_, disposable) = self.additionalLatestArchiveHoleDisposable { + removed.append(disposable) + } + if let additionalLatestArchiveHole = additionalLatestArchiveHole { + let disposable = MetaDisposable() + self.additionalLatestArchiveHoleDisposable = (additionalLatestArchiveHole, disposable) + addedAdditionalLatestArchiveHole = (additionalLatestArchiveHole, disposable) + } + } + } } - return (removed, added, addedAdditionalLatestHole) + return (removed, added, addedAdditionalLatestHole, addedAdditionalLatestArchiveHole) } } @@ -55,20 +70,25 @@ func managedChatListHoles(network: Network, postbox: Postbox, accountPeerId: Pee let state = Atomic(value: ManagedChatListHolesState()) let topRootHoleKey: PostboxViewKey = .allChatListHoles(.root) + let topArchiveHoleKey: PostboxViewKey = .allChatListHoles(Namespaces.PeerGroup.archive) let filtersKey: PostboxViewKey = .preferences(keys: Set([PreferencesKeys.chatListFilters])) - let combinedView = postbox.combinedView(keys: [topRootHoleKey, filtersKey]) + let combinedView = postbox.combinedView(keys: [topRootHoleKey, topArchiveHoleKey, filtersKey]) let disposable = combineLatest(postbox.chatListHolesView(), combinedView).start(next: { view, combinedView in var additionalLatestHole: ChatListHole? + var additionalLatestArchiveHole: ChatListHole? if let preferencesView = combinedView.views[filtersKey] as? PreferencesView, let filtersState = preferencesView.values[PreferencesKeys.chatListFilters] as? ChatListFiltersState, !filtersState.filters.isEmpty { if let topRootHole = combinedView.views[topRootHoleKey] as? AllChatListHolesView { additionalLatestHole = topRootHole.latestHole } + if let topArchiveHole = combinedView.views[topArchiveHoleKey] as? AllChatListHolesView { + additionalLatestArchiveHole = topArchiveHole.latestHole + } } - let (removed, added, addedAdditionalLatestHole) = state.with { state in - return state.update(entries: view.entries, additionalLatestHole: additionalLatestHole) + let (removed, added, addedAdditionalLatestHole, addedAdditionalLatestArchiveHole) = state.with { state in + return state.update(entries: view.entries, additionalLatestHole: additionalLatestHole, additionalLatestArchiveHole: additionalLatestArchiveHole) } for disposable in removed { @@ -82,6 +102,10 @@ func managedChatListHoles(network: Network, postbox: Postbox, accountPeerId: Pee if let (hole, disposable) = addedAdditionalLatestHole { disposable.set(fetchChatListHole(postbox: postbox, network: network, accountPeerId: accountPeerId, groupId: .root, hole: hole).start()) } + + if let (hole, disposable) = addedAdditionalLatestArchiveHole { + disposable.set(fetchChatListHole(postbox: postbox, network: network, accountPeerId: accountPeerId, groupId: .root, hole: hole).start()) + } }) return ActionDisposable { diff --git a/submodules/TelegramCore/Sources/PendingMessageUploadedContent.swift b/submodules/TelegramCore/Sources/PendingMessageUploadedContent.swift index bcf231fa2f..ae508078d9 100644 --- a/submodules/TelegramCore/Sources/PendingMessageUploadedContent.swift +++ b/submodules/TelegramCore/Sources/PendingMessageUploadedContent.swift @@ -177,6 +177,9 @@ func mediaContentToUpload(network: Network, postbox: Postbox, auxiliaryMethods: } let inputPoll = Api.InputMedia.inputMediaPoll(flags: pollMediaFlags, poll: Api.Poll.poll(id: 0, flags: pollFlags, question: poll.text, answers: poll.options.map({ $0.apiOption })), correctAnswers: correctAnswers) return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(inputPoll, text), reuploadInfo: nil))) + } else if let dice = media as? TelegramMediaDice { + let input = Api.InputMedia.inputMediaDice + return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(input, text), reuploadInfo: nil))) } else { return nil } diff --git a/submodules/TelegramCore/Sources/Serialization.swift b/submodules/TelegramCore/Sources/Serialization.swift index 15b20dd3d8..ca3b511329 100644 --- a/submodules/TelegramCore/Sources/Serialization.swift +++ b/submodules/TelegramCore/Sources/Serialization.swift @@ -210,7 +210,7 @@ public class BoxedMessage: NSObject { public class Serialization: NSObject, MTSerialization { public func currentLayer() -> UInt { - return 110 + return 111 } public func parseMessage(_ data: Data!) -> Any! { diff --git a/submodules/TelegramCore/Sources/StoreMessage_Telegram.swift b/submodules/TelegramCore/Sources/StoreMessage_Telegram.swift index 500ec09591..2b229a142e 100644 --- a/submodules/TelegramCore/Sources/StoreMessage_Telegram.swift +++ b/submodules/TelegramCore/Sources/StoreMessage_Telegram.swift @@ -342,8 +342,8 @@ func textMediaAndExpirationTimerFromApiMedia(_ media: Api.MessageMedia?, _ peerI } return (TelegramMediaPoll(pollId: MediaId(namespace: Namespaces.Media.CloudPoll, id: id), publicity: publicity, kind: kind, text: question, options: answers.map(TelegramMediaPollOption.init(apiOption:)), correctAnswers: nil, results: TelegramMediaPollResults(apiResults: results), isClosed: (flags & (1 << 0)) != 0), nil) } - case .messageMediaDice: - break + case let .messageMediaDice(value): + return (TelegramMediaDice(value: value), nil) } } diff --git a/submodules/TelegramCore/Sources/UpdateCachedPeerData.swift b/submodules/TelegramCore/Sources/UpdateCachedPeerData.swift index e5c836e12e..60d0f48f57 100644 --- a/submodules/TelegramCore/Sources/UpdateCachedPeerData.swift +++ b/submodules/TelegramCore/Sources/UpdateCachedPeerData.swift @@ -319,7 +319,7 @@ func fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPeerId: PeerI } switch fullChat { - case let .channelFull(flags, _, about, participantsCount, adminsCount, kickedCount, bannedCount, _, _, _, _, _, _, apiExportedInvite, apiBotInfos, migratedFromChatId, migratedFromMaxId, pinnedMsgId, stickerSet, minAvailableMsgId, folderId, linkedChatId, location, slowmodeSeconds, slowmodeNextSendDate, _, pts): + case let .channelFull(flags, _, about, participantsCount, adminsCount, kickedCount, bannedCount, _, _, _, _, _, _, apiExportedInvite, apiBotInfos, migratedFromChatId, migratedFromMaxId, pinnedMsgId, stickerSet, minAvailableMsgId, folderId, linkedChatId, location, slowmodeSeconds, slowmodeNextSendDate, statsDc, pts): var channelFlags = CachedChannelFlags() if (flags & (1 << 3)) != 0 { channelFlags.insert(.canDisplayParticipants) @@ -450,7 +450,7 @@ func fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPeerId: PeerI .withUpdatedSlowModeTimeout(slowmodeSeconds) .withUpdatedSlowModeValidUntilTimestamp(slowmodeNextSendDate) .withUpdatedHasScheduledMessages(hasScheduledMessages) -// .withUpdatedStatsDatacenterId(statsDc ?? 0) + .withUpdatedStatsDatacenterId(statsDc ?? 0) }) if let minAvailableMessageId = minAvailableMessageId, minAvailableMessageIdUpdated { diff --git a/submodules/TelegramPresentationData/Sources/ComponentsThemes.swift b/submodules/TelegramPresentationData/Sources/ComponentsThemes.swift index 51b824d8ad..075fa1a452 100644 --- a/submodules/TelegramPresentationData/Sources/ComponentsThemes.swift +++ b/submodules/TelegramPresentationData/Sources/ComponentsThemes.swift @@ -40,7 +40,7 @@ public extension PresentationFontSize { public extension TabBarControllerTheme { convenience init(rootControllerTheme: PresentationTheme) { let theme = rootControllerTheme.rootController.tabBar - self.init(backgroundColor: rootControllerTheme.list.plainBackgroundColor, tabBarBackgroundColor: theme.backgroundColor, tabBarSeparatorColor: theme.separatorColor, tabBarIconColor: theme.iconColor, tabBarSelectedIconColor: theme.selectedIconColor, tabBarTextColor: theme.textColor, tabBarSelectedTextColor: theme.selectedTextColor, tabBarBadgeBackgroundColor: theme.badgeBackgroundColor, tabBarBadgeStrokeColor: theme.badgeStrokeColor, tabBarBadgeTextColor: theme.badgeTextColor) + self.init(backgroundColor: rootControllerTheme.list.plainBackgroundColor, tabBarBackgroundColor: theme.backgroundColor, tabBarSeparatorColor: theme.separatorColor, tabBarIconColor: theme.iconColor, tabBarSelectedIconColor: theme.selectedIconColor, tabBarTextColor: theme.textColor, tabBarSelectedTextColor: theme.selectedTextColor, tabBarBadgeBackgroundColor: theme.badgeBackgroundColor, tabBarBadgeStrokeColor: theme.badgeStrokeColor, tabBarBadgeTextColor: theme.badgeTextColor, tabBarExtractedIconColor: rootControllerTheme.contextMenu.extractedContentTintColor, tabBarExtractedTextColor: rootControllerTheme.contextMenu.extractedContentTintColor) } } diff --git a/submodules/TelegramPresentationData/Sources/DefaultDarkPresentationTheme.swift b/submodules/TelegramPresentationData/Sources/DefaultDarkPresentationTheme.swift index 0814168886..6304a86943 100644 --- a/submodules/TelegramPresentationData/Sources/DefaultDarkPresentationTheme.swift +++ b/submodules/TelegramPresentationData/Sources/DefaultDarkPresentationTheme.swift @@ -520,7 +520,10 @@ public func makeDefaultDarkPresentationTheme(extendingThemeReference: Presentati itemHighlightedBackgroundColor: UIColor(rgb: 0xffffff, alpha: 0.15), primaryColor: UIColor(rgb: 0xffffff, alpha: 1.0), secondaryColor: UIColor(rgb: 0xffffff, alpha: 0.8), - destructiveColor: UIColor(rgb: 0xeb5545) + destructiveColor: UIColor(rgb: 0xeb5545), + badgeFillColor: UIColor(rgb: 0xeb5545), + badgeForegroundColor: UIColor(rgb: 0xffffff, alpha: 1.0), + extractedContentTintColor: UIColor(rgb: 0x252525, alpha: 0.78) ) let inAppNotification = PresentationThemeInAppNotification( diff --git a/submodules/TelegramPresentationData/Sources/DefaultDarkTintedPresentationTheme.swift b/submodules/TelegramPresentationData/Sources/DefaultDarkTintedPresentationTheme.swift index 0781eba55a..f3c6970e6a 100644 --- a/submodules/TelegramPresentationData/Sources/DefaultDarkTintedPresentationTheme.swift +++ b/submodules/TelegramPresentationData/Sources/DefaultDarkTintedPresentationTheme.swift @@ -769,7 +769,10 @@ public func makeDefaultDarkTintedPresentationTheme(extendingThemeReference: Pres itemHighlightedBackgroundColor: UIColor(rgb: 0xffffff, alpha: 0.15), primaryColor: UIColor(rgb: 0xffffff, alpha: 1.0), secondaryColor: UIColor(rgb: 0xffffff, alpha: 0.8), - destructiveColor: UIColor(rgb: 0xff6767) + destructiveColor: UIColor(rgb: 0xff6767), + badgeFillColor: UIColor(rgb: 0xff6767), + badgeForegroundColor: UIColor(rgb: 0xffffff, alpha: 1.0), + extractedContentTintColor: rootNavigationBar.backgroundColor.withAlphaComponent(0.78) ) let inAppNotification = PresentationThemeInAppNotification( diff --git a/submodules/TelegramPresentationData/Sources/DefaultDayPresentationTheme.swift b/submodules/TelegramPresentationData/Sources/DefaultDayPresentationTheme.swift index d18947cb4d..0522f7a94b 100644 --- a/submodules/TelegramPresentationData/Sources/DefaultDayPresentationTheme.swift +++ b/submodules/TelegramPresentationData/Sources/DefaultDayPresentationTheme.swift @@ -731,7 +731,10 @@ public func makeDefaultDayPresentationTheme(extendingThemeReference: Presentatio itemHighlightedBackgroundColor: UIColor(rgb: 0x3c3c43, alpha: 0.2), primaryColor: UIColor(rgb: 0x000000, alpha: 1.0), secondaryColor: UIColor(rgb: 0x000000, alpha: 0.8), - destructiveColor: UIColor(rgb: 0xff3b30) + destructiveColor: UIColor(rgb: 0xff3b30), + badgeFillColor: UIColor(rgb: 0xff3b30), + badgeForegroundColor: UIColor(rgb: 0xffffff, alpha: 1.0), + extractedContentTintColor: .white ) let inAppNotification = PresentationThemeInAppNotification( diff --git a/submodules/TelegramPresentationData/Sources/PresentationStrings.swift b/submodules/TelegramPresentationData/Sources/PresentationStrings.swift index df5ea4620e..7cfafefe1a 100644 --- a/submodules/TelegramPresentationData/Sources/PresentationStrings.swift +++ b/submodules/TelegramPresentationData/Sources/PresentationStrings.swift @@ -788,4573 +788,4592 @@ public final class PresentationStrings: Equatable { public var AuthSessions_EmptyTitle: String { return self._s[549]! } public var Privacy_PhoneNumber: String { return self._s[550]! } public var PeopleNearby_CreateGroup: String { return self._s[551]! } - public var CallSettings_UseLessData: String { return self._s[553]! } - public var NetworkUsageSettings_MediaDocumentDataSection: String { return self._s[554]! } - public var Stickers_AddToFavorites: String { return self._s[555]! } - public var Wallet_WordImport_Title: String { return self._s[556]! } - public var PhotoEditor_QualityLow: String { return self._s[557]! } - public var Watch_UserInfo_Unblock: String { return self._s[558]! } - public var Settings_Logout: String { return self._s[559]! } + public var Stats_SharesPerPost: String { return self._s[553]! } + public var CallSettings_UseLessData: String { return self._s[554]! } + public var NetworkUsageSettings_MediaDocumentDataSection: String { return self._s[555]! } + public var Stickers_AddToFavorites: String { return self._s[556]! } + public var Wallet_WordImport_Title: String { return self._s[557]! } + public var PhotoEditor_QualityLow: String { return self._s[558]! } + public var Watch_UserInfo_Unblock: String { return self._s[559]! } + public var Settings_Logout: String { return self._s[560]! } public func PUSH_MESSAGE_ROUND(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[560]!, self._r[560]!, [_1]) + return formatWithArgumentRanges(self._s[561]!, self._r[561]!, [_1]) } - public var ContactInfo_PhoneLabelWork: String { return self._s[561]! } - public var ChannelInfo_Stats: String { return self._s[562]! } - public var TextFormat_Link: String { return self._s[563]! } + public var ContactInfo_PhoneLabelWork: String { return self._s[562]! } + public var ChannelInfo_Stats: String { return self._s[563]! } + public var TextFormat_Link: String { return self._s[564]! } public func Date_ChatDateHeader(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[564]!, self._r[564]!, [_1, _2]) + return formatWithArgumentRanges(self._s[565]!, self._r[565]!, [_1, _2]) } - public var Wallet_TransactionInfo_Title: String { return self._s[565]! } + public var Wallet_TransactionInfo_Title: String { return self._s[566]! } public func Message_ForwardedMessage(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[566]!, self._r[566]!, [_0]) + return formatWithArgumentRanges(self._s[567]!, self._r[567]!, [_0]) } - public var Watch_Notification_Joined: String { return self._s[567]! } - public var Group_Setup_TypePublicHelp: String { return self._s[568]! } - public var Passport_Scans_UploadNew: String { return self._s[569]! } - public var Checkout_LiabilityAlertTitle: String { return self._s[570]! } - public var DialogList_Title: String { return self._s[573]! } - public var NotificationSettings_ContactJoined: String { return self._s[574]! } - public var GroupInfo_LabelAdmin: String { return self._s[575]! } - public var KeyCommand_ChatInfo: String { return self._s[576]! } - public var Conversation_EditingCaptionPanelTitle: String { return self._s[577]! } - public var Call_ReportIncludeLog: String { return self._s[578]! } + public var Watch_Notification_Joined: String { return self._s[568]! } + public var Group_Setup_TypePublicHelp: String { return self._s[569]! } + public var Passport_Scans_UploadNew: String { return self._s[570]! } + public var Checkout_LiabilityAlertTitle: String { return self._s[571]! } + public var DialogList_Title: String { return self._s[574]! } + public var NotificationSettings_ContactJoined: String { return self._s[575]! } + public var GroupInfo_LabelAdmin: String { return self._s[576]! } + public var KeyCommand_ChatInfo: String { return self._s[577]! } + public var Conversation_EditingCaptionPanelTitle: String { return self._s[578]! } + public var Call_ReportIncludeLog: String { return self._s[579]! } public func Notifications_ExceptionsChangeSound(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[581]!, self._r[581]!, [_0]) + return formatWithArgumentRanges(self._s[582]!, self._r[582]!, [_0]) } - public var Channel_AdminLog_InfoPanelChannelAlertText: String { return self._s[582]! } - public var ChatAdmins_AllMembersAreAdmins: String { return self._s[583]! } - public var LocalGroup_IrrelevantWarning: String { return self._s[584]! } - public var Conversation_DefaultRestrictedInline: String { return self._s[585]! } - public var Message_Sticker: String { return self._s[586]! } - public var LastSeen_JustNow: String { return self._s[588]! } - public var Passport_Email_EmailPlaceholder: String { return self._s[590]! } - public var SettingsSearch_Synonyms_AppLanguage: String { return self._s[591]! } - public var Channel_AdminLogFilter_EventsEditedMessages: String { return self._s[592]! } - public var Channel_EditAdmin_PermissionsHeader: String { return self._s[593]! } - public var TwoStepAuth_Email: String { return self._s[594]! } - public var SettingsSearch_Synonyms_Notifications_ChannelNotificationsSound: String { return self._s[595]! } - public var PhotoEditor_BlurToolOff: String { return self._s[596]! } - public var Message_PinnedStickerMessage: String { return self._s[597]! } - public var ContactInfo_PhoneLabelPager: String { return self._s[598]! } - public var SettingsSearch_Synonyms_Appearance_TextSize: String { return self._s[599]! } - public var Passport_DiscardMessageTitle: String { return self._s[600]! } - public var Privacy_PaymentsTitle: String { return self._s[601]! } - public var EditTheme_Edit_Preview_IncomingReplyName: String { return self._s[602]! } - public var ClearCache_StorageCache: String { return self._s[603]! } - public var Appearance_TextSizeSetting: String { return self._s[604]! } - public var Channel_DiscussionGroup_Header: String { return self._s[606]! } - public var VoiceOver_Chat_OptionSelected: String { return self._s[607]! } - public var Appearance_ColorTheme: String { return self._s[608]! } - public var UserInfo_ShareContact: String { return self._s[609]! } - public var Passport_Address_TypePassportRegistration: String { return self._s[610]! } - public var Common_More: String { return self._s[611]! } - public var Watch_Message_Call: String { return self._s[612]! } - public var Profile_EncryptionKey: String { return self._s[615]! } - public var Privacy_TopPeers: String { return self._s[616]! } - public var Conversation_StopPollConfirmation: String { return self._s[617]! } - public var Wallet_Words_NotDoneText: String { return self._s[619]! } - public var Privacy_TopPeersWarning: String { return self._s[621]! } - public var SettingsSearch_Synonyms_Data_DownloadInBackground: String { return self._s[622]! } - public var SettingsSearch_Synonyms_Data_Storage_KeepMedia: String { return self._s[623]! } - public var Wallet_RestoreFailed_EnterWords: String { return self._s[626]! } - public var DialogList_SearchSectionMessages: String { return self._s[627]! } - public var Notifications_ChannelNotifications: String { return self._s[628]! } - public var CheckoutInfo_ShippingInfoAddress1Placeholder: String { return self._s[629]! } - public var Passport_Language_sk: String { return self._s[630]! } - public var Notification_MessageLifetime1h: String { return self._s[631]! } - public var Wallpaper_ResetWallpapersInfo: String { return self._s[632]! } - public var Appearance_ThemePreview_Chat_5_Text: String { return self._s[633]! } - public var Call_ReportSkip: String { return self._s[635]! } - public var Cache_ServiceFiles: String { return self._s[636]! } - public var Group_ErrorAddTooMuchAdmins: String { return self._s[637]! } - public var VoiceOver_Chat_YourFile: String { return self._s[638]! } - public var Map_Hybrid: String { return self._s[639]! } - public var Contacts_SearchUsersAndGroupsLabel: String { return self._s[641]! } + public var Stats_Followers: String { return self._s[583]! } + public var Channel_AdminLog_InfoPanelChannelAlertText: String { return self._s[584]! } + public var ChatAdmins_AllMembersAreAdmins: String { return self._s[585]! } + public var LocalGroup_IrrelevantWarning: String { return self._s[586]! } + public var Conversation_DefaultRestrictedInline: String { return self._s[587]! } + public var Message_Sticker: String { return self._s[588]! } + public var LastSeen_JustNow: String { return self._s[590]! } + public var Passport_Email_EmailPlaceholder: String { return self._s[592]! } + public var SettingsSearch_Synonyms_AppLanguage: String { return self._s[593]! } + public var Channel_AdminLogFilter_EventsEditedMessages: String { return self._s[594]! } + public var Channel_EditAdmin_PermissionsHeader: String { return self._s[595]! } + public var TwoStepAuth_Email: String { return self._s[596]! } + public var SettingsSearch_Synonyms_Notifications_ChannelNotificationsSound: String { return self._s[597]! } + public var PhotoEditor_BlurToolOff: String { return self._s[598]! } + public var Message_PinnedStickerMessage: String { return self._s[599]! } + public var ContactInfo_PhoneLabelPager: String { return self._s[600]! } + public var SettingsSearch_Synonyms_Appearance_TextSize: String { return self._s[601]! } + public var Passport_DiscardMessageTitle: String { return self._s[602]! } + public var Privacy_PaymentsTitle: String { return self._s[603]! } + public var EditTheme_Edit_Preview_IncomingReplyName: String { return self._s[604]! } + public var ClearCache_StorageCache: String { return self._s[605]! } + public var Appearance_TextSizeSetting: String { return self._s[606]! } + public var Channel_DiscussionGroup_Header: String { return self._s[608]! } + public var VoiceOver_Chat_OptionSelected: String { return self._s[609]! } + public var Appearance_ColorTheme: String { return self._s[610]! } + public var UserInfo_ShareContact: String { return self._s[611]! } + public var Passport_Address_TypePassportRegistration: String { return self._s[612]! } + public var Common_More: String { return self._s[613]! } + public var Watch_Message_Call: String { return self._s[614]! } + public var Profile_EncryptionKey: String { return self._s[617]! } + public var Privacy_TopPeers: String { return self._s[618]! } + public var Conversation_StopPollConfirmation: String { return self._s[619]! } + public var Wallet_Words_NotDoneText: String { return self._s[621]! } + public var Privacy_TopPeersWarning: String { return self._s[623]! } + public var SettingsSearch_Synonyms_Data_DownloadInBackground: String { return self._s[624]! } + public var SettingsSearch_Synonyms_Data_Storage_KeepMedia: String { return self._s[625]! } + public var Wallet_RestoreFailed_EnterWords: String { return self._s[628]! } + public var DialogList_SearchSectionMessages: String { return self._s[629]! } + public var Notifications_ChannelNotifications: String { return self._s[630]! } + public var CheckoutInfo_ShippingInfoAddress1Placeholder: String { return self._s[631]! } + public var Passport_Language_sk: String { return self._s[632]! } + public var Notification_MessageLifetime1h: String { return self._s[633]! } + public var Wallpaper_ResetWallpapersInfo: String { return self._s[634]! } + public var Appearance_ThemePreview_Chat_5_Text: String { return self._s[635]! } + public var Call_ReportSkip: String { return self._s[637]! } + public var Cache_ServiceFiles: String { return self._s[638]! } + public var Group_ErrorAddTooMuchAdmins: String { return self._s[639]! } + public var VoiceOver_Chat_YourFile: String { return self._s[640]! } + public var Map_Hybrid: String { return self._s[641]! } + public var Contacts_SearchUsersAndGroupsLabel: String { return self._s[643]! } public func PUSH_MESSAGE_QUIZ(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[642]!, self._r[642]!, [_1]) + return formatWithArgumentRanges(self._s[644]!, self._r[644]!, [_1]) } - public var ChatSettings_AutoDownloadVideos: String { return self._s[644]! } - public var Channel_BanUser_PermissionEmbedLinks: String { return self._s[645]! } - public var InfoPlist_NSLocationAlwaysAndWhenInUseUsageDescription: String { return self._s[646]! } - public var SocksProxySetup_ProxyTelegram: String { return self._s[649]! } + public var ChatSettings_AutoDownloadVideos: String { return self._s[646]! } + public var Channel_BanUser_PermissionEmbedLinks: String { return self._s[647]! } + public var InfoPlist_NSLocationAlwaysAndWhenInUseUsageDescription: String { return self._s[648]! } + public var SocksProxySetup_ProxyTelegram: String { return self._s[651]! } public func PUSH_MESSAGE_AUDIO(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[650]!, self._r[650]!, [_1]) + return formatWithArgumentRanges(self._s[652]!, self._r[652]!, [_1]) } - public var Channel_Username_CreatePrivateLinkHelp: String { return self._s[652]! } - public var ScheduledMessages_ScheduledToday: String { return self._s[653]! } + public var Channel_Username_CreatePrivateLinkHelp: String { return self._s[654]! } + public var ScheduledMessages_ScheduledToday: String { return self._s[655]! } public func PUSH_CHAT_TITLE_EDITED(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[654]!, self._r[654]!, [_1, _2]) + return formatWithArgumentRanges(self._s[656]!, self._r[656]!, [_1, _2]) } - public var Conversation_LiveLocationYou: String { return self._s[655]! } - public var SettingsSearch_Synonyms_Privacy_Calls: String { return self._s[656]! } - public var SettingsSearch_Synonyms_Notifications_MessageNotificationsPreview: String { return self._s[657]! } - public var UserInfo_ShareBot: String { return self._s[660]! } + public var Conversation_LiveLocationYou: String { return self._s[657]! } + public var SettingsSearch_Synonyms_Privacy_Calls: String { return self._s[658]! } + public var SettingsSearch_Synonyms_Notifications_MessageNotificationsPreview: String { return self._s[659]! } + public var UserInfo_ShareBot: String { return self._s[662]! } public func PUSH_AUTH_REGION(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[661]!, self._r[661]!, [_1, _2]) + return formatWithArgumentRanges(self._s[663]!, self._r[663]!, [_1, _2]) } - public var Conversation_ClearCache: String { return self._s[662]! } - public var PhotoEditor_ShadowsTint: String { return self._s[663]! } - public var Message_Audio: String { return self._s[664]! } - public var Passport_Language_lt: String { return self._s[665]! } + public var Conversation_ClearCache: String { return self._s[664]! } + public var PhotoEditor_ShadowsTint: String { return self._s[665]! } + public var Message_Audio: String { return self._s[666]! } + public var Passport_Language_lt: String { return self._s[667]! } public func Message_PinnedTextMessage(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[666]!, self._r[666]!, [_0]) + return formatWithArgumentRanges(self._s[668]!, self._r[668]!, [_0]) } - public var Permissions_SiriText_v0: String { return self._s[667]! } - public var Conversation_FileICloudDrive: String { return self._s[668]! } - public var ChatList_DeleteForEveryoneConfirmationTitle: String { return self._s[669]! } - public var Notifications_Badge_IncludeMutedChats: String { return self._s[670]! } + public var Permissions_SiriText_v0: String { return self._s[669]! } + public var Conversation_FileICloudDrive: String { return self._s[670]! } + public var ChatList_DeleteForEveryoneConfirmationTitle: String { return self._s[671]! } + public var Notifications_Badge_IncludeMutedChats: String { return self._s[672]! } public func Notification_NewAuthDetected(_ _1: String, _ _2: String, _ _3: String, _ _4: String, _ _5: String, _ _6: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[671]!, self._r[671]!, [_1, _2, _3, _4, _5, _6]) + return formatWithArgumentRanges(self._s[673]!, self._r[673]!, [_1, _2, _3, _4, _5, _6]) } - public var DialogList_ProxyConnectionIssuesTooltip: String { return self._s[672]! } + public var DialogList_ProxyConnectionIssuesTooltip: String { return self._s[674]! } public func Time_MonthOfYear_m5(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[673]!, self._r[673]!, [_0]) + return formatWithArgumentRanges(self._s[675]!, self._r[675]!, [_0]) } - public var Channel_SignMessages: String { return self._s[674]! } + public var Channel_SignMessages: String { return self._s[676]! } public func PUSH_MESSAGE_NOTEXT(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[675]!, self._r[675]!, [_1]) + return formatWithArgumentRanges(self._s[677]!, self._r[677]!, [_1]) } - public var Compose_ChannelTokenListPlaceholder: String { return self._s[676]! } - public var Passport_ScanPassport: String { return self._s[677]! } - public var Watch_Suggestion_Thanks: String { return self._s[678]! } - public var BlockedUsers_AddNew: String { return self._s[679]! } + public var Compose_ChannelTokenListPlaceholder: String { return self._s[678]! } + public var Passport_ScanPassport: String { return self._s[679]! } + public var Watch_Suggestion_Thanks: String { return self._s[680]! } + public var BlockedUsers_AddNew: String { return self._s[681]! } public func PUSH_CHAT_MESSAGE(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[680]!, self._r[680]!, [_1, _2]) + return formatWithArgumentRanges(self._s[682]!, self._r[682]!, [_1, _2]) } - public var Watch_Message_Invoice: String { return self._s[681]! } - public var SettingsSearch_Synonyms_Privacy_LastSeen: String { return self._s[682]! } - public var Month_GenJuly: String { return self._s[683]! } - public var CreatePoll_QuizInfo: String { return self._s[684]! } - public var UserInfo_StartSecretChatStart: String { return self._s[685]! } - public var SocksProxySetup_ProxySocks5: String { return self._s[686]! } - public var IntentsSettings_SuggestByShare: String { return self._s[688]! } - public var Notification_Exceptions_DeleteAllConfirmation: String { return self._s[689]! } - public var Notification_ChannelInviterSelf: String { return self._s[690]! } - public var CheckoutInfo_ReceiverInfoEmail: String { return self._s[691]! } + public var Watch_Message_Invoice: String { return self._s[683]! } + public var SettingsSearch_Synonyms_Privacy_LastSeen: String { return self._s[684]! } + public var Month_GenJuly: String { return self._s[685]! } + public var CreatePoll_QuizInfo: String { return self._s[686]! } + public var UserInfo_StartSecretChatStart: String { return self._s[687]! } + public var SocksProxySetup_ProxySocks5: String { return self._s[688]! } + public var IntentsSettings_SuggestByShare: String { return self._s[690]! } + public var Notification_Exceptions_DeleteAllConfirmation: String { return self._s[691]! } + public var Notification_ChannelInviterSelf: String { return self._s[692]! } + public var CheckoutInfo_ReceiverInfoEmail: String { return self._s[693]! } public func ApplyLanguage_ChangeLanguageUnofficialText(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[692]!, self._r[692]!, [_1, _2]) + return formatWithArgumentRanges(self._s[694]!, self._r[694]!, [_1, _2]) } - public var CheckoutInfo_Title: String { return self._s[693]! } - public var Watch_Stickers_RecentPlaceholder: String { return self._s[694]! } + public var Stats_FollowersTitle: String { return self._s[695]! } + public var CheckoutInfo_Title: String { return self._s[696]! } + public var Watch_Stickers_RecentPlaceholder: String { return self._s[697]! } public func Map_DistanceAway(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[695]!, self._r[695]!, [_0]) + return formatWithArgumentRanges(self._s[698]!, self._r[698]!, [_0]) } - public var Passport_Identity_MainPage: String { return self._s[696]! } - public var TwoStepAuth_ConfirmEmailResendCode: String { return self._s[697]! } - public var Passport_Language_de: String { return self._s[698]! } - public var PeerInfo_PaneVoice: String { return self._s[699]! } - public var Update_Title: String { return self._s[700]! } - public var ContactInfo_PhoneLabelWorkFax: String { return self._s[701]! } - public var Channel_AdminLog_BanEmbedLinks: String { return self._s[702]! } - public var Passport_Email_UseTelegramEmailHelp: String { return self._s[703]! } - public var Notifications_ChannelNotificationsPreview: String { return self._s[704]! } - public var NotificationsSound_Telegraph: String { return self._s[705]! } - public var Watch_LastSeen_ALongTimeAgo: String { return self._s[706]! } - public var ChannelMembers_WhoCanAddMembers: String { return self._s[707]! } + public var Passport_Identity_MainPage: String { return self._s[699]! } + public var TwoStepAuth_ConfirmEmailResendCode: String { return self._s[700]! } + public var Passport_Language_de: String { return self._s[701]! } + public var PeerInfo_PaneVoice: String { return self._s[702]! } + public var Update_Title: String { return self._s[703]! } + public var ContactInfo_PhoneLabelWorkFax: String { return self._s[704]! } + public var Channel_AdminLog_BanEmbedLinks: String { return self._s[705]! } + public var Passport_Email_UseTelegramEmailHelp: String { return self._s[706]! } + public var Notifications_ChannelNotificationsPreview: String { return self._s[707]! } + public var NotificationsSound_Telegraph: String { return self._s[708]! } + public var Watch_LastSeen_ALongTimeAgo: String { return self._s[709]! } + public var ChannelMembers_WhoCanAddMembers: String { return self._s[710]! } public func AutoDownloadSettings_UpTo(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[708]!, self._r[708]!, [_0]) + return formatWithArgumentRanges(self._s[711]!, self._r[711]!, [_0]) } - public var ClearCache_Description: String { return self._s[709]! } - public var Stickers_SuggestAll: String { return self._s[710]! } - public var Conversation_ForwardTitle: String { return self._s[711]! } - public var Appearance_ThemePreview_ChatList_7_Name: String { return self._s[712]! } + public var ClearCache_Description: String { return self._s[712]! } + public var Stickers_SuggestAll: String { return self._s[713]! } + public var Conversation_ForwardTitle: String { return self._s[714]! } + public var Appearance_ThemePreview_ChatList_7_Name: String { return self._s[715]! } public func Notification_JoinedChannel(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[713]!, self._r[713]!, [_0]) + return formatWithArgumentRanges(self._s[716]!, self._r[716]!, [_0]) } - public var Calls_NewCall: String { return self._s[714]! } - public var Call_StatusEnded: String { return self._s[715]! } - public var AutoDownloadSettings_DataUsageLow: String { return self._s[716]! } - public var Settings_ProxyConnected: String { return self._s[717]! } - public var Channel_AdminLogFilter_EventsPinned: String { return self._s[718]! } - public var PhotoEditor_QualityVeryLow: String { return self._s[719]! } - public var Channel_AdminLogFilter_EventsDeletedMessages: String { return self._s[720]! } - public var Passport_PasswordPlaceholder: String { return self._s[721]! } - public var Message_PinnedInvoice: String { return self._s[722]! } - public var Passport_Identity_IssueDate: String { return self._s[723]! } - public var Passport_Language_pl: String { return self._s[724]! } + public var Calls_NewCall: String { return self._s[717]! } + public var Call_StatusEnded: String { return self._s[718]! } + public var AutoDownloadSettings_DataUsageLow: String { return self._s[719]! } + public var Settings_ProxyConnected: String { return self._s[720]! } + public var Channel_AdminLogFilter_EventsPinned: String { return self._s[721]! } + public var PhotoEditor_QualityVeryLow: String { return self._s[722]! } + public var Channel_AdminLogFilter_EventsDeletedMessages: String { return self._s[723]! } + public var Passport_PasswordPlaceholder: String { return self._s[724]! } + public var Message_PinnedInvoice: String { return self._s[725]! } + public var Passport_Identity_IssueDate: String { return self._s[726]! } + public var Passport_Language_pl: String { return self._s[727]! } public func ChannelInfo_ChannelForbidden(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[725]!, self._r[725]!, [_0]) - } - public var Call_StatusConnecting: String { return self._s[726]! } - public var SocksProxySetup_PasteFromClipboard: String { return self._s[727]! } - public func Username_UsernameIsAvailable(_ _0: String) -> (String, [(Int, NSRange)]) { return formatWithArgumentRanges(self._s[728]!, self._r[728]!, [_0]) } - public var ChatSettings_ConnectionType_UseProxy: String { return self._s[730]! } - public var Common_Edit: String { return self._s[731]! } - public var PrivacySettings_LastSeenNobody: String { return self._s[732]! } + public var Call_StatusConnecting: String { return self._s[729]! } + public var SocksProxySetup_PasteFromClipboard: String { return self._s[730]! } + public func Username_UsernameIsAvailable(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[731]!, self._r[731]!, [_0]) + } + public var ChatSettings_ConnectionType_UseProxy: String { return self._s[733]! } + public var Common_Edit: String { return self._s[734]! } + public var PrivacySettings_LastSeenNobody: String { return self._s[735]! } public func Notification_LeftChat(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[733]!, self._r[733]!, [_0]) + return formatWithArgumentRanges(self._s[736]!, self._r[736]!, [_0]) } - public var GroupInfo_ChatAdmins: String { return self._s[734]! } - public var PrivateDataSettings_Title: String { return self._s[735]! } - public var Login_CancelPhoneVerificationStop: String { return self._s[736]! } - public var ChatList_Read: String { return self._s[737]! } - public var Wallet_WordImport_Text: String { return self._s[738]! } - public var Undo_ChatClearedForBothSides: String { return self._s[739]! } - public var GroupPermission_SectionTitle: String { return self._s[740]! } - public var TwoFactorSetup_Intro_Title: String { return self._s[742]! } + public var GroupInfo_ChatAdmins: String { return self._s[737]! } + public var PrivateDataSettings_Title: String { return self._s[738]! } + public var Login_CancelPhoneVerificationStop: String { return self._s[739]! } + public var ChatList_Read: String { return self._s[740]! } + public var Wallet_WordImport_Text: String { return self._s[741]! } + public var Undo_ChatClearedForBothSides: String { return self._s[742]! } + public var GroupPermission_SectionTitle: String { return self._s[743]! } + public var TwoFactorSetup_Intro_Title: String { return self._s[745]! } public func PUSH_CHAT_LEFT(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[743]!, self._r[743]!, [_1, _2]) + return formatWithArgumentRanges(self._s[746]!, self._r[746]!, [_1, _2]) } - public var Checkout_ErrorPaymentFailed: String { return self._s[744]! } - public var Update_UpdateApp: String { return self._s[745]! } - public var Group_Username_RevokeExistingUsernamesInfo: String { return self._s[746]! } - public var Settings_Appearance: String { return self._s[747]! } - public var SettingsSearch_Synonyms_Stickers_SuggestStickers: String { return self._s[751]! } - public var Watch_Location_Access: String { return self._s[752]! } - public var ShareMenu_CopyShareLink: String { return self._s[754]! } - public var TwoStepAuth_SetupHintTitle: String { return self._s[755]! } - public var Conversation_Theme: String { return self._s[757]! } + public var Checkout_ErrorPaymentFailed: String { return self._s[747]! } + public var Update_UpdateApp: String { return self._s[748]! } + public var Group_Username_RevokeExistingUsernamesInfo: String { return self._s[749]! } + public var Settings_Appearance: String { return self._s[750]! } + public var SettingsSearch_Synonyms_Stickers_SuggestStickers: String { return self._s[754]! } + public var Watch_Location_Access: String { return self._s[755]! } + public var ShareMenu_CopyShareLink: String { return self._s[757]! } + public var TwoStepAuth_SetupHintTitle: String { return self._s[758]! } + public var Conversation_Theme: String { return self._s[760]! } public func DialogList_SingleRecordingVideoMessageSuffix(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[758]!, self._r[758]!, [_0]) + return formatWithArgumentRanges(self._s[761]!, self._r[761]!, [_0]) } - public var Notifications_ClassicTones: String { return self._s[759]! } - public var Weekday_ShortWednesday: String { return self._s[760]! } - public var WallpaperPreview_SwipeColorsBottomText: String { return self._s[761]! } - public var Undo_LeftGroup: String { return self._s[764]! } - public var Wallet_RestoreFailed_Text: String { return self._s[765]! } - public var Conversation_LinkDialogCopy: String { return self._s[766]! } - public var Wallet_TransactionInfo_NoAddress: String { return self._s[768]! } - public var Wallet_Navigation_Back: String { return self._s[769]! } - public var KeyCommand_FocusOnInputField: String { return self._s[770]! } - public var Contacts_SelectAll: String { return self._s[771]! } - public var Preview_SaveToCameraRoll: String { return self._s[772]! } - public var PrivacySettings_PasscodeOff: String { return self._s[773]! } - public var Appearance_ThemePreview_ChatList_6_Name: String { return self._s[774]! } + public var Notifications_ClassicTones: String { return self._s[762]! } + public var Weekday_ShortWednesday: String { return self._s[763]! } + public var WallpaperPreview_SwipeColorsBottomText: String { return self._s[764]! } + public var Undo_LeftGroup: String { return self._s[767]! } + public var Wallet_RestoreFailed_Text: String { return self._s[768]! } + public var Conversation_LinkDialogCopy: String { return self._s[769]! } + public var Wallet_TransactionInfo_NoAddress: String { return self._s[771]! } + public var Wallet_Navigation_Back: String { return self._s[772]! } + public var KeyCommand_FocusOnInputField: String { return self._s[773]! } + public var Contacts_SelectAll: String { return self._s[774]! } + public var Preview_SaveToCameraRoll: String { return self._s[775]! } + public var PrivacySettings_PasscodeOff: String { return self._s[776]! } + public var Appearance_ThemePreview_ChatList_6_Name: String { return self._s[777]! } public func PUSH_CHANNEL_MESSAGE_QUIZ(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[775]!, self._r[775]!, [_1]) + return formatWithArgumentRanges(self._s[778]!, self._r[778]!, [_1]) } - public var Wallpaper_Title: String { return self._s[776]! } - public var Conversation_FilePhotoOrVideo: String { return self._s[777]! } - public var AccessDenied_Camera: String { return self._s[778]! } - public var Watch_Compose_CurrentLocation: String { return self._s[779]! } - public var PeerInfo_ButtonMessage: String { return self._s[781]! } - public var Channel_DiscussionGroup_MakeHistoryPublicProceed: String { return self._s[782]! } + public var Wallpaper_Title: String { return self._s[779]! } + public var Conversation_FilePhotoOrVideo: String { return self._s[780]! } + public var AccessDenied_Camera: String { return self._s[781]! } + public var Watch_Compose_CurrentLocation: String { return self._s[782]! } + public var PeerInfo_ButtonMessage: String { return self._s[784]! } + public var Channel_DiscussionGroup_MakeHistoryPublicProceed: String { return self._s[785]! } public func SecretImage_NotViewedYet(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[783]!, self._r[783]!, [_0]) + return formatWithArgumentRanges(self._s[786]!, self._r[786]!, [_0]) } - public var GroupInfo_InvitationLinkDoesNotExist: String { return self._s[784]! } - public var Passport_Language_ro: String { return self._s[785]! } - public var EditTheme_UploadNewTheme: String { return self._s[786]! } - public var CheckoutInfo_SaveInfoHelp: String { return self._s[787]! } - public var Wallet_Intro_Terms: String { return self._s[788]! } + public var GroupInfo_InvitationLinkDoesNotExist: String { return self._s[787]! } + public var Passport_Language_ro: String { return self._s[788]! } + public var EditTheme_UploadNewTheme: String { return self._s[789]! } + public var CheckoutInfo_SaveInfoHelp: String { return self._s[790]! } + public var Wallet_Intro_Terms: String { return self._s[791]! } public func Notification_SecretChatMessageScreenshot(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[789]!, self._r[789]!, [_0]) + return formatWithArgumentRanges(self._s[792]!, self._r[792]!, [_0]) } - public var Login_CancelPhoneVerification: String { return self._s[790]! } - public var State_ConnectingToProxy: String { return self._s[791]! } - public var Calls_RatingTitle: String { return self._s[792]! } - public var Generic_ErrorMoreInfo: String { return self._s[793]! } - public var ChatList_Search_ShowMore: String { return self._s[794]! } - public var Appearance_PreviewReplyText: String { return self._s[795]! } - public var CheckoutInfo_ShippingInfoPostcodePlaceholder: String { return self._s[796]! } + public var Login_CancelPhoneVerification: String { return self._s[793]! } + public var State_ConnectingToProxy: String { return self._s[794]! } + public var Calls_RatingTitle: String { return self._s[795]! } + public var Generic_ErrorMoreInfo: String { return self._s[796]! } + public var ChatList_Search_ShowMore: String { return self._s[797]! } + public var Appearance_PreviewReplyText: String { return self._s[798]! } + public var CheckoutInfo_ShippingInfoPostcodePlaceholder: String { return self._s[799]! } public func Wallet_Send_Balance(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[797]!, self._r[797]!, [_0]) + return formatWithArgumentRanges(self._s[800]!, self._r[800]!, [_0]) } - public var IntentsSettings_SuggestedChatsContacts: String { return self._s[798]! } - public var SharedMedia_CategoryLinks: String { return self._s[799]! } - public var Calls_Missed: String { return self._s[800]! } - public var Cache_Photos: String { return self._s[804]! } - public var GroupPermission_NoAddMembers: String { return self._s[805]! } - public var ScheduledMessages_Title: String { return self._s[806]! } + public var IntentsSettings_SuggestedChatsContacts: String { return self._s[801]! } + public var SharedMedia_CategoryLinks: String { return self._s[802]! } + public var Calls_Missed: String { return self._s[803]! } + public var Cache_Photos: String { return self._s[807]! } + public var GroupPermission_NoAddMembers: String { return self._s[808]! } + public var ScheduledMessages_Title: String { return self._s[809]! } public func Channel_AdminLog_MessageUnpinned(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[807]!, self._r[807]!, [_0]) + return formatWithArgumentRanges(self._s[810]!, self._r[810]!, [_0]) } - public var Conversation_ShareBotLocationConfirmationTitle: String { return self._s[808]! } - public var Settings_ProxyDisabled: String { return self._s[809]! } + public var Conversation_ShareBotLocationConfirmationTitle: String { return self._s[811]! } + public var Settings_ProxyDisabled: String { return self._s[812]! } public func Settings_ApplyProxyAlertCredentials(_ _1: String, _ _2: String, _ _3: String, _ _4: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[810]!, self._r[810]!, [_1, _2, _3, _4]) + return formatWithArgumentRanges(self._s[813]!, self._r[813]!, [_1, _2, _3, _4]) } public func Conversation_RestrictedMediaTimed(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[811]!, self._r[811]!, [_0]) + return formatWithArgumentRanges(self._s[814]!, self._r[814]!, [_0]) } - public var ChatList_Context_RemoveFromRecents: String { return self._s[813]! } - public var Appearance_Title: String { return self._s[814]! } + public var Stats_ViewsPerPost: String { return self._s[816]! } + public var ChatList_Context_RemoveFromRecents: String { return self._s[817]! } + public var Appearance_Title: String { return self._s[818]! } public func Time_MonthOfYear_m2(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[816]!, self._r[816]!, [_0]) + return formatWithArgumentRanges(self._s[820]!, self._r[820]!, [_0]) } - public var Conversation_WalletRequiredText: String { return self._s[817]! } - public var StickerPacksSettings_ShowStickersButtonHelp: String { return self._s[818]! } - public var OldChannels_NoticeCreateText: String { return self._s[819]! } - public var Channel_EditMessageErrorGeneric: String { return self._s[820]! } - public var Privacy_Calls_IntegrationHelp: String { return self._s[821]! } - public var Preview_DeletePhoto: String { return self._s[822]! } - public var Appearance_AppIconFilledX: String { return self._s[823]! } - public var PrivacySettings_PrivacyTitle: String { return self._s[824]! } + public var Conversation_WalletRequiredText: String { return self._s[821]! } + public var StickerPacksSettings_ShowStickersButtonHelp: String { return self._s[822]! } + public var OldChannels_NoticeCreateText: String { return self._s[823]! } + public var Channel_EditMessageErrorGeneric: String { return self._s[824]! } + public var Privacy_Calls_IntegrationHelp: String { return self._s[825]! } + public var Preview_DeletePhoto: String { return self._s[826]! } + public var Appearance_AppIconFilledX: String { return self._s[827]! } + public var PrivacySettings_PrivacyTitle: String { return self._s[828]! } public func Conversation_BotInteractiveUrlAlert(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[825]!, self._r[825]!, [_0]) + return formatWithArgumentRanges(self._s[829]!, self._r[829]!, [_0]) } - public var Coub_TapForSound: String { return self._s[828]! } - public var Map_LocatingError: String { return self._s[829]! } - public var TwoStepAuth_EmailChangeSuccess: String { return self._s[831]! } - public var Conversation_SendMessage_SendSilently: String { return self._s[832]! } - public var VoiceOver_MessageContextOpenMessageMenu: String { return self._s[833]! } + public var Coub_TapForSound: String { return self._s[832]! } + public var Map_LocatingError: String { return self._s[833]! } + public var TwoStepAuth_EmailChangeSuccess: String { return self._s[835]! } + public var Conversation_SendMessage_SendSilently: String { return self._s[836]! } + public var VoiceOver_MessageContextOpenMessageMenu: String { return self._s[837]! } public func Wallet_Time_PreciseDate_m8(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[834]!, self._r[834]!, [_1, _2, _3]) + return formatWithArgumentRanges(self._s[838]!, self._r[838]!, [_1, _2, _3]) } - public var Passport_ForgottenPassword: String { return self._s[835]! } - public var GroupInfo_InviteLink_RevokeLink: String { return self._s[836]! } - public var StickerPacksSettings_ArchivedPacks: String { return self._s[837]! } - public var Login_TermsOfServiceSignupDecline: String { return self._s[839]! } - public var Channel_Moderator_AccessLevelRevoke: String { return self._s[840]! } - public var Message_Location: String { return self._s[841]! } - public var Passport_Identity_NamePlaceholder: String { return self._s[842]! } - public var Channel_Management_Title: String { return self._s[843]! } - public var DialogList_SearchSectionDialogs: String { return self._s[845]! } - public var Compose_NewChannel_Members: String { return self._s[846]! } + public var Passport_ForgottenPassword: String { return self._s[839]! } + public var GroupInfo_InviteLink_RevokeLink: String { return self._s[840]! } + public var StickerPacksSettings_ArchivedPacks: String { return self._s[841]! } + public var Login_TermsOfServiceSignupDecline: String { return self._s[843]! } + public var Channel_Moderator_AccessLevelRevoke: String { return self._s[844]! } + public var Message_Location: String { return self._s[845]! } + public var Passport_Identity_NamePlaceholder: String { return self._s[846]! } + public var Channel_Management_Title: String { return self._s[847]! } + public var DialogList_SearchSectionDialogs: String { return self._s[849]! } + public var Compose_NewChannel_Members: String { return self._s[850]! } public func DialogList_SingleUploadingFileSuffix(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[847]!, self._r[847]!, [_0]) + return formatWithArgumentRanges(self._s[851]!, self._r[851]!, [_0]) } - public var GroupInfo_Location: String { return self._s[848]! } - public var Appearance_ThemePreview_ChatList_5_Name: String { return self._s[849]! } - public var ClearCache_Clear: String { return self._s[850]! } - public var AutoNightTheme_ScheduledFrom: String { return self._s[851]! } - public var PhotoEditor_WarmthTool: String { return self._s[852]! } - public var Passport_Language_tr: String { return self._s[853]! } + public var GroupInfo_Location: String { return self._s[852]! } + public var Appearance_ThemePreview_ChatList_5_Name: String { return self._s[853]! } + public var ClearCache_Clear: String { return self._s[854]! } + public var AutoNightTheme_ScheduledFrom: String { return self._s[855]! } + public var PhotoEditor_WarmthTool: String { return self._s[856]! } + public var Passport_Language_tr: String { return self._s[857]! } public func PUSH_MESSAGE_GAME_SCORE(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[854]!, self._r[854]!, [_1, _2, _3]) + return formatWithArgumentRanges(self._s[858]!, self._r[858]!, [_1, _2, _3]) } - public var OldChannels_NoticeUpgradeText: String { return self._s[855]! } - public var Login_ResetAccountProtected_Reset: String { return self._s[857]! } - public var Watch_PhotoView_Title: String { return self._s[858]! } - public var Passport_Phone_Delete: String { return self._s[859]! } - public var Undo_ChatDeletedForBothSides: String { return self._s[860]! } - public var Conversation_EditingMessageMediaEditCurrentPhoto: String { return self._s[861]! } - public var GroupInfo_Permissions: String { return self._s[862]! } - public var PasscodeSettings_TurnPasscodeOff: String { return self._s[863]! } - public var Profile_ShareContactButton: String { return self._s[864]! } - public var ChatSettings_Other: String { return self._s[865]! } - public var UserInfo_NotificationsDisabled: String { return self._s[866]! } - public var CheckoutInfo_ShippingInfoCity: String { return self._s[867]! } - public var LastSeen_WithinAMonth: String { return self._s[868]! } - public var VoiceOver_Chat_PlayHint: String { return self._s[869]! } - public var Conversation_ReportGroupLocation: String { return self._s[870]! } - public var Conversation_EncryptionCanceled: String { return self._s[871]! } - public var MediaPicker_GroupDescription: String { return self._s[872]! } - public var WebSearch_Images: String { return self._s[873]! } + public var OldChannels_NoticeUpgradeText: String { return self._s[859]! } + public var Login_ResetAccountProtected_Reset: String { return self._s[861]! } + public var Watch_PhotoView_Title: String { return self._s[862]! } + public var Passport_Phone_Delete: String { return self._s[863]! } + public var Undo_ChatDeletedForBothSides: String { return self._s[864]! } + public var Conversation_EditingMessageMediaEditCurrentPhoto: String { return self._s[865]! } + public var GroupInfo_Permissions: String { return self._s[866]! } + public var PasscodeSettings_TurnPasscodeOff: String { return self._s[867]! } + public var Profile_ShareContactButton: String { return self._s[868]! } + public var ChatSettings_Other: String { return self._s[869]! } + public var UserInfo_NotificationsDisabled: String { return self._s[870]! } + public var CheckoutInfo_ShippingInfoCity: String { return self._s[871]! } + public var LastSeen_WithinAMonth: String { return self._s[872]! } + public var VoiceOver_Chat_PlayHint: String { return self._s[873]! } + public var Conversation_ReportGroupLocation: String { return self._s[874]! } + public var Conversation_EncryptionCanceled: String { return self._s[875]! } + public var MediaPicker_GroupDescription: String { return self._s[876]! } + public var WebSearch_Images: String { return self._s[877]! } public func Channel_Management_PromotedBy(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[874]!, self._r[874]!, [_0]) + return formatWithArgumentRanges(self._s[878]!, self._r[878]!, [_0]) } - public var Message_Photo: String { return self._s[875]! } - public var PasscodeSettings_HelpBottom: String { return self._s[876]! } - public var AutoDownloadSettings_VideosTitle: String { return self._s[877]! } - public var VoiceOver_Media_PlaybackRateChange: String { return self._s[878]! } - public var Passport_Identity_AddDriversLicense: String { return self._s[879]! } - public var TwoStepAuth_EnterPasswordPassword: String { return self._s[880]! } - public var NotificationsSound_Calypso: String { return self._s[881]! } - public var Map_Map: String { return self._s[882]! } + public var Message_Photo: String { return self._s[879]! } + public var PasscodeSettings_HelpBottom: String { return self._s[880]! } + public var AutoDownloadSettings_VideosTitle: String { return self._s[881]! } + public var VoiceOver_Media_PlaybackRateChange: String { return self._s[882]! } + public var Passport_Identity_AddDriversLicense: String { return self._s[883]! } + public var TwoStepAuth_EnterPasswordPassword: String { return self._s[884]! } + public var NotificationsSound_Calypso: String { return self._s[885]! } + public var Map_Map: String { return self._s[886]! } public func Conversation_LiveLocationYouAndOther(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[883]!, self._r[883]!, [_0]) - } - public var CheckoutInfo_ReceiverInfoTitle: String { return self._s[885]! } - public var ChatSettings_TextSizeUnits: String { return self._s[886]! } - public func VoiceOver_Chat_FileFrom(_ _0: String) -> (String, [(Int, NSRange)]) { return formatWithArgumentRanges(self._s[887]!, self._r[887]!, [_0]) } - public var Common_of: String { return self._s[888]! } - public var Conversation_ForwardContacts: String { return self._s[891]! } - public var IntentsSettings_SuggestByAll: String { return self._s[893]! } + public var CheckoutInfo_ReceiverInfoTitle: String { return self._s[889]! } + public var ChatSettings_TextSizeUnits: String { return self._s[890]! } + public func VoiceOver_Chat_FileFrom(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[891]!, self._r[891]!, [_0]) + } + public var Common_of: String { return self._s[892]! } + public var Conversation_ForwardContacts: String { return self._s[895]! } + public var IntentsSettings_SuggestByAll: String { return self._s[897]! } public func Call_AnsweringWithAccount(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[894]!, self._r[894]!, [_0]) + return formatWithArgumentRanges(self._s[898]!, self._r[898]!, [_0]) } - public var Passport_Language_hy: String { return self._s[895]! } - public var Notifications_MessageNotificationsHelp: String { return self._s[896]! } - public var AutoDownloadSettings_Reset: String { return self._s[897]! } - public var Wallet_TransactionInfo_AddressCopied: String { return self._s[898]! } - public var Paint_ClearConfirm: String { return self._s[899]! } - public var Camera_VideoMode: String { return self._s[900]! } + public var Passport_Language_hy: String { return self._s[899]! } + public var Notifications_MessageNotificationsHelp: String { return self._s[900]! } + public var AutoDownloadSettings_Reset: String { return self._s[901]! } + public var Wallet_TransactionInfo_AddressCopied: String { return self._s[902]! } + public var Paint_ClearConfirm: String { return self._s[903]! } + public var Camera_VideoMode: String { return self._s[904]! } public func Conversation_RestrictedStickersTimed(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[901]!, self._r[901]!, [_0]) + return formatWithArgumentRanges(self._s[905]!, self._r[905]!, [_0]) } - public var Privacy_Calls_AlwaysAllow_Placeholder: String { return self._s[902]! } - public var Conversation_ViewBackground: String { return self._s[903]! } + public var Privacy_Calls_AlwaysAllow_Placeholder: String { return self._s[906]! } + public var Conversation_ViewBackground: String { return self._s[907]! } public func Wallet_Info_TransactionDateHeaderYear(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[904]!, self._r[904]!, [_1, _2, _3]) + return formatWithArgumentRanges(self._s[908]!, self._r[908]!, [_1, _2, _3]) } - public var Passport_Language_el: String { return self._s[905]! } - public var PhotoEditor_Original: String { return self._s[906]! } - public var Settings_FAQ_Button: String { return self._s[908]! } - public var Channel_Setup_PublicNoLink: String { return self._s[910]! } - public var Conversation_UnsupportedMedia: String { return self._s[911]! } - public var Conversation_SlideToCancel: String { return self._s[912]! } - public var Appearance_ThemePreview_ChatList_4_Name: String { return self._s[913]! } - public var Passport_Identity_OneOfTypeInternalPassport: String { return self._s[914]! } - public var CheckoutInfo_ShippingInfoPostcode: String { return self._s[915]! } - public var Conversation_ReportSpamChannelConfirmation: String { return self._s[916]! } - public var AutoNightTheme_NotAvailable: String { return self._s[917]! } - public var Conversation_Owner: String { return self._s[918]! } - public var Common_Create: String { return self._s[919]! } - public var Settings_ApplyProxyAlertEnable: String { return self._s[920]! } - public var ContactList_Context_Call: String { return self._s[921]! } - public var Localization_ChooseLanguage: String { return self._s[923]! } - public var ChatList_Context_AddToContacts: String { return self._s[925]! } - public var OldChannels_NoticeTitle: String { return self._s[926]! } - public var Settings_Proxy: String { return self._s[928]! } - public var Privacy_TopPeersHelp: String { return self._s[929]! } - public var CheckoutInfo_ShippingInfoCountryPlaceholder: String { return self._s[930]! } - public var Chat_UnsendMyMessages: String { return self._s[931]! } + public var Passport_Language_el: String { return self._s[909]! } + public var PhotoEditor_Original: String { return self._s[910]! } + public var Settings_FAQ_Button: String { return self._s[912]! } + public var Channel_Setup_PublicNoLink: String { return self._s[914]! } + public var Conversation_UnsupportedMedia: String { return self._s[915]! } + public var Conversation_SlideToCancel: String { return self._s[916]! } + public var Appearance_ThemePreview_ChatList_4_Name: String { return self._s[917]! } + public var Passport_Identity_OneOfTypeInternalPassport: String { return self._s[918]! } + public var CheckoutInfo_ShippingInfoPostcode: String { return self._s[919]! } + public var Conversation_ReportSpamChannelConfirmation: String { return self._s[920]! } + public var AutoNightTheme_NotAvailable: String { return self._s[921]! } + public var Conversation_Owner: String { return self._s[922]! } + public var Common_Create: String { return self._s[923]! } + public var Settings_ApplyProxyAlertEnable: String { return self._s[924]! } + public var ContactList_Context_Call: String { return self._s[925]! } + public var Localization_ChooseLanguage: String { return self._s[927]! } + public var ChatList_Context_AddToContacts: String { return self._s[929]! } + public var OldChannels_NoticeTitle: String { return self._s[930]! } + public var Settings_Proxy: String { return self._s[932]! } + public var Privacy_TopPeersHelp: String { return self._s[933]! } + public var CheckoutInfo_ShippingInfoCountryPlaceholder: String { return self._s[934]! } + public var Chat_UnsendMyMessages: String { return self._s[935]! } public func VoiceOver_Chat_Duration(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[932]!, self._r[932]!, [_0]) + return formatWithArgumentRanges(self._s[936]!, self._r[936]!, [_0]) } - public var TwoStepAuth_ConfirmationAbort: String { return self._s[933]! } + public var TwoStepAuth_ConfirmationAbort: String { return self._s[937]! } public func Contacts_AccessDeniedHelpPortrait(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[935]!, self._r[935]!, [_0]) - } - public var Contacts_SortedByPresence: String { return self._s[936]! } - public var Passport_Identity_SurnamePlaceholder: String { return self._s[937]! } - public var Cache_Title: String { return self._s[938]! } - public func Login_PhoneBannedEmailSubject(_ _0: String) -> (String, [(Int, NSRange)]) { return formatWithArgumentRanges(self._s[939]!, self._r[939]!, [_0]) } - public var TwoStepAuth_EmailCodeExpired: String { return self._s[940]! } - public var Channel_Moderator_Title: String { return self._s[941]! } - public var InstantPage_AutoNightTheme: String { return self._s[943]! } + public var Contacts_SortedByPresence: String { return self._s[940]! } + public var Passport_Identity_SurnamePlaceholder: String { return self._s[941]! } + public var Cache_Title: String { return self._s[942]! } + public func Login_PhoneBannedEmailSubject(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[943]!, self._r[943]!, [_0]) + } + public var TwoStepAuth_EmailCodeExpired: String { return self._s[944]! } + public var Channel_Moderator_Title: String { return self._s[945]! } + public var InstantPage_AutoNightTheme: String { return self._s[947]! } public func PUSH_MESSAGE_POLL(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[946]!, self._r[946]!, [_1]) + return formatWithArgumentRanges(self._s[950]!, self._r[950]!, [_1]) } - public var Passport_Scans_Upload: String { return self._s[947]! } - public var Undo_Undo: String { return self._s[949]! } - public var Contacts_AccessDeniedHelpON: String { return self._s[950]! } - public var TwoStepAuth_RemovePassword: String { return self._s[951]! } - public var Common_Delete: String { return self._s[952]! } - public var Contacts_AddPeopleNearby: String { return self._s[954]! } - public var Conversation_ContextMenuDelete: String { return self._s[955]! } - public var SocksProxySetup_Credentials: String { return self._s[956]! } - public var Appearance_EditTheme: String { return self._s[958]! } - public var ClearCache_StorageOtherApps: String { return self._s[959]! } - public var PasscodeSettings_AutoLock_Disabled: String { return self._s[960]! } - public var Wallet_Send_NetworkErrorText: String { return self._s[961]! } - public var AuthSessions_DevicesTitle: String { return self._s[963]! } - public var Passport_Address_OneOfTypeRentalAgreement: String { return self._s[965]! } - public var Conversation_ShareBotContactConfirmationTitle: String { return self._s[966]! } - public var Passport_Language_id: String { return self._s[968]! } - public var WallpaperSearch_ColorTeal: String { return self._s[969]! } - public var ChannelIntro_Title: String { return self._s[970]! } + public var Passport_Scans_Upload: String { return self._s[951]! } + public var Undo_Undo: String { return self._s[953]! } + public var Contacts_AccessDeniedHelpON: String { return self._s[954]! } + public var TwoStepAuth_RemovePassword: String { return self._s[955]! } + public var Common_Delete: String { return self._s[956]! } + public var Contacts_AddPeopleNearby: String { return self._s[958]! } + public var Conversation_ContextMenuDelete: String { return self._s[959]! } + public var SocksProxySetup_Credentials: String { return self._s[960]! } + public var Appearance_EditTheme: String { return self._s[962]! } + public var ClearCache_StorageOtherApps: String { return self._s[963]! } + public var PasscodeSettings_AutoLock_Disabled: String { return self._s[964]! } + public var Wallet_Send_NetworkErrorText: String { return self._s[965]! } + public var AuthSessions_DevicesTitle: String { return self._s[967]! } + public var Passport_Address_OneOfTypeRentalAgreement: String { return self._s[969]! } + public var Conversation_ShareBotContactConfirmationTitle: String { return self._s[970]! } + public var Passport_Language_id: String { return self._s[972]! } + public var WallpaperSearch_ColorTeal: String { return self._s[973]! } + public var ChannelIntro_Title: String { return self._s[974]! } public func Channel_AdminLog_MessageToggleSignaturesOff(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[971]!, self._r[971]!, [_0]) + return formatWithArgumentRanges(self._s[975]!, self._r[975]!, [_0]) } - public var VoiceOver_Chat_OpenLinkHint: String { return self._s[973]! } - public var VoiceOver_Chat_Reply: String { return self._s[974]! } - public var ScheduledMessages_BotActionUnavailable: String { return self._s[975]! } - public var Channel_Info_Description: String { return self._s[976]! } - public var Stickers_FavoriteStickers: String { return self._s[977]! } - public var Channel_BanUser_PermissionAddMembers: String { return self._s[978]! } - public var Notifications_DisplayNamesOnLockScreen: String { return self._s[979]! } - public var ChatSearch_ResultsTooltip: String { return self._s[980]! } - public var Wallet_VoiceOver_Editing_ClearText: String { return self._s[981]! } - public var Calls_NoMissedCallsPlacehoder: String { return self._s[982]! } - public var Group_PublicLink_Placeholder: String { return self._s[983]! } - public var Notifications_ExceptionsDefaultSound: String { return self._s[984]! } + public var VoiceOver_Chat_OpenLinkHint: String { return self._s[977]! } + public var VoiceOver_Chat_Reply: String { return self._s[978]! } + public var ScheduledMessages_BotActionUnavailable: String { return self._s[979]! } + public var Channel_Info_Description: String { return self._s[980]! } + public var Stickers_FavoriteStickers: String { return self._s[981]! } + public var Channel_BanUser_PermissionAddMembers: String { return self._s[982]! } + public var Notifications_DisplayNamesOnLockScreen: String { return self._s[983]! } + public var ChatSearch_ResultsTooltip: String { return self._s[984]! } + public var Wallet_VoiceOver_Editing_ClearText: String { return self._s[985]! } + public var Calls_NoMissedCallsPlacehoder: String { return self._s[986]! } + public var Group_PublicLink_Placeholder: String { return self._s[987]! } + public var Notifications_ExceptionsDefaultSound: String { return self._s[988]! } public func PUSH_CHANNEL_MESSAGE_POLL(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[985]!, self._r[985]!, [_1]) + return formatWithArgumentRanges(self._s[989]!, self._r[989]!, [_1]) } - public var TextFormat_Underline: String { return self._s[986]! } + public var TextFormat_Underline: String { return self._s[990]! } public func DialogList_SearchSubtitleFormat(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[988]!, self._r[988]!, [_1, _2]) + return formatWithArgumentRanges(self._s[992]!, self._r[992]!, [_1, _2]) } public func Channel_AdminLog_MessageRemovedGroupStickerPack(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[989]!, self._r[989]!, [_0]) + return formatWithArgumentRanges(self._s[993]!, self._r[993]!, [_0]) } - public var Appearance_ThemePreview_ChatList_3_Name: String { return self._s[990]! } + public var Appearance_ThemePreview_ChatList_3_Name: String { return self._s[994]! } public func Channel_OwnershipTransfer_TransferCompleted(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[991]!, self._r[991]!, [_1, _2]) + return formatWithArgumentRanges(self._s[995]!, self._r[995]!, [_1, _2]) } - public var Wallet_Intro_ImportExisting: String { return self._s[992]! } - public var GroupPermission_Delete: String { return self._s[993]! } - public var Passport_Language_uk: String { return self._s[994]! } - public var StickerPack_HideStickers: String { return self._s[996]! } - public var ChangePhoneNumberNumber_NumberPlaceholder: String { return self._s[997]! } + public var Wallet_Intro_ImportExisting: String { return self._s[996]! } + public var GroupPermission_Delete: String { return self._s[997]! } + public var Passport_Language_uk: String { return self._s[998]! } + public var StickerPack_HideStickers: String { return self._s[1000]! } + public var ChangePhoneNumberNumber_NumberPlaceholder: String { return self._s[1001]! } public func PUSH_CHAT_MESSAGE_PHOTO(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[998]!, self._r[998]!, [_1, _2]) + return formatWithArgumentRanges(self._s[1002]!, self._r[1002]!, [_1, _2]) } - public var Activity_UploadingVideoMessage: String { return self._s[999]! } + public var Activity_UploadingVideoMessage: String { return self._s[1003]! } public func GroupPermission_ApplyAlertText(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1000]!, self._r[1000]!, [_0]) + return formatWithArgumentRanges(self._s[1004]!, self._r[1004]!, [_0]) } - public var Channel_TitleInfo: String { return self._s[1001]! } - public var StickerPacksSettings_ArchivedPacks_Info: String { return self._s[1002]! } - public var Settings_CallSettings: String { return self._s[1003]! } - public var Camera_SquareMode: String { return self._s[1004]! } - public var Conversation_SendMessage_ScheduleMessage: String { return self._s[1005]! } - public var GroupInfo_SharedMediaNone: String { return self._s[1006]! } + public var Channel_TitleInfo: String { return self._s[1005]! } + public var StickerPacksSettings_ArchivedPacks_Info: String { return self._s[1006]! } + public var Settings_CallSettings: String { return self._s[1007]! } + public var Camera_SquareMode: String { return self._s[1008]! } + public var Conversation_SendMessage_ScheduleMessage: String { return self._s[1009]! } + public var GroupInfo_SharedMediaNone: String { return self._s[1010]! } public func PUSH_MESSAGE_VIDEO_SECRET(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1007]!, self._r[1007]!, [_1]) + return formatWithArgumentRanges(self._s[1011]!, self._r[1011]!, [_1]) } - public var Bot_GenericBotStatus: String { return self._s[1008]! } - public var Application_Update: String { return self._s[1010]! } - public var Month_ShortJanuary: String { return self._s[1011]! } - public var Contacts_PermissionsKeepDisabled: String { return self._s[1012]! } - public var Channel_AdminLog_BanReadMessages: String { return self._s[1013]! } - public var Settings_AppLanguage_Unofficial: String { return self._s[1014]! } - public var Passport_Address_Street2Placeholder: String { return self._s[1015]! } + public var Bot_GenericBotStatus: String { return self._s[1012]! } + public var Application_Update: String { return self._s[1014]! } + public var Month_ShortJanuary: String { return self._s[1015]! } + public var Contacts_PermissionsKeepDisabled: String { return self._s[1016]! } + public var Channel_AdminLog_BanReadMessages: String { return self._s[1017]! } + public var Settings_AppLanguage_Unofficial: String { return self._s[1018]! } + public var Passport_Address_Street2Placeholder: String { return self._s[1019]! } public func Map_LiveLocationShortHour(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1016]!, self._r[1016]!, [_0]) + return formatWithArgumentRanges(self._s[1020]!, self._r[1020]!, [_0]) } - public var NetworkUsageSettings_Cellular: String { return self._s[1017]! } - public var Appearance_PreviewOutgoingText: String { return self._s[1018]! } + public var NetworkUsageSettings_Cellular: String { return self._s[1021]! } + public var Appearance_PreviewOutgoingText: String { return self._s[1022]! } public func StickerPackActionInfo_RemovedText(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1019]!, self._r[1019]!, [_0]) + return formatWithArgumentRanges(self._s[1023]!, self._r[1023]!, [_0]) } - public var Notifications_PermissionsAllowInSettings: String { return self._s[1020]! } - public var AutoDownloadSettings_OnForAll: String { return self._s[1022]! } - public var Map_Directions: String { return self._s[1023]! } - public var Passport_FieldIdentityTranslationHelp: String { return self._s[1025]! } - public var Appearance_ThemeDay: String { return self._s[1026]! } - public var LogoutOptions_LogOut: String { return self._s[1027]! } - public var Group_PublicLink_Title: String { return self._s[1029]! } - public var Channel_AddBotErrorNoRights: String { return self._s[1030]! } - public var ChatList_Search_ShowLess: String { return self._s[1031]! } - public var Passport_Identity_AddPassport: String { return self._s[1032]! } - public var LocalGroup_ButtonTitle: String { return self._s[1033]! } - public var Call_Message: String { return self._s[1034]! } - public var PhotoEditor_ExposureTool: String { return self._s[1035]! } - public var Wallet_Receive_CommentInfo: String { return self._s[1037]! } - public var Passport_FieldOneOf_Delimeter: String { return self._s[1038]! } - public var Channel_AdminLog_CanBanUsers: String { return self._s[1040]! } - public var Appearance_ThemePreview_ChatList_2_Name: String { return self._s[1041]! } - public var Appearance_Preview: String { return self._s[1042]! } - public var Compose_ChannelMembers: String { return self._s[1043]! } - public var Conversation_DeleteManyMessages: String { return self._s[1044]! } - public var ReportPeer_ReasonOther_Title: String { return self._s[1045]! } - public var Checkout_ErrorProviderAccountTimeout: String { return self._s[1046]! } - public var TwoStepAuth_ResetAccountConfirmation: String { return self._s[1047]! } - public var Channel_Stickers_CreateYourOwn: String { return self._s[1050]! } - public var Conversation_UpdateTelegram: String { return self._s[1051]! } - public var EditTheme_Create_TopInfo: String { return self._s[1052]! } + public var Notifications_PermissionsAllowInSettings: String { return self._s[1024]! } + public var AutoDownloadSettings_OnForAll: String { return self._s[1026]! } + public var Map_Directions: String { return self._s[1027]! } + public var Passport_FieldIdentityTranslationHelp: String { return self._s[1029]! } + public var Appearance_ThemeDay: String { return self._s[1030]! } + public var LogoutOptions_LogOut: String { return self._s[1031]! } + public var Group_PublicLink_Title: String { return self._s[1033]! } + public var Channel_AddBotErrorNoRights: String { return self._s[1034]! } + public var ChatList_Search_ShowLess: String { return self._s[1035]! } + public var Passport_Identity_AddPassport: String { return self._s[1036]! } + public var LocalGroup_ButtonTitle: String { return self._s[1037]! } + public var Stats_InteractionsTitle: String { return self._s[1038]! } + public var Call_Message: String { return self._s[1039]! } + public var PhotoEditor_ExposureTool: String { return self._s[1040]! } + public var Wallet_Receive_CommentInfo: String { return self._s[1042]! } + public var Passport_FieldOneOf_Delimeter: String { return self._s[1043]! } + public var Channel_AdminLog_CanBanUsers: String { return self._s[1045]! } + public var Appearance_ThemePreview_ChatList_2_Name: String { return self._s[1046]! } + public var Appearance_Preview: String { return self._s[1047]! } + public var Compose_ChannelMembers: String { return self._s[1048]! } + public var Conversation_DeleteManyMessages: String { return self._s[1049]! } + public var ReportPeer_ReasonOther_Title: String { return self._s[1050]! } + public var Checkout_ErrorProviderAccountTimeout: String { return self._s[1051]! } + public var TwoStepAuth_ResetAccountConfirmation: String { return self._s[1052]! } + public var Channel_Stickers_CreateYourOwn: String { return self._s[1055]! } + public var Conversation_UpdateTelegram: String { return self._s[1056]! } + public var EditTheme_Create_TopInfo: String { return self._s[1057]! } public func Notification_PinnedPhotoMessage(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1053]!, self._r[1053]!, [_0]) + return formatWithArgumentRanges(self._s[1058]!, self._r[1058]!, [_0]) } - public var Wallet_WordCheck_Continue: String { return self._s[1054]! } - public var TwoFactorSetup_Hint_Action: String { return self._s[1055]! } - public var IntentsSettings_ResetAll: String { return self._s[1056]! } + public var Wallet_WordCheck_Continue: String { return self._s[1059]! } + public var TwoFactorSetup_Hint_Action: String { return self._s[1060]! } + public var IntentsSettings_ResetAll: String { return self._s[1061]! } public func PUSH_PINNED_GIF(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1057]!, self._r[1057]!, [_1]) + return formatWithArgumentRanges(self._s[1062]!, self._r[1062]!, [_1]) } - public var GroupInfo_Administrators_Title: String { return self._s[1058]! } - public var Privacy_Forwards_PreviewMessageText: String { return self._s[1059]! } + public var GroupInfo_Administrators_Title: String { return self._s[1063]! } + public var Privacy_Forwards_PreviewMessageText: String { return self._s[1064]! } public func PrivacySettings_LastSeenNobodyPlus(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1060]!, self._r[1060]!, [_0]) + return formatWithArgumentRanges(self._s[1065]!, self._r[1065]!, [_0]) } - public var Tour_Title3: String { return self._s[1061]! } - public var Channel_EditAdmin_PermissionInviteSubscribers: String { return self._s[1062]! } - public var Clipboard_SendPhoto: String { return self._s[1066]! } - public var MediaPicker_Videos: String { return self._s[1067]! } - public var Passport_Email_Title: String { return self._s[1068]! } + public var Tour_Title3: String { return self._s[1066]! } + public var Channel_EditAdmin_PermissionInviteSubscribers: String { return self._s[1067]! } + public var Clipboard_SendPhoto: String { return self._s[1071]! } + public var MediaPicker_Videos: String { return self._s[1072]! } + public var Passport_Email_Title: String { return self._s[1073]! } public func PrivacySettings_LastSeenEverybodyMinus(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1069]!, self._r[1069]!, [_0]) + return formatWithArgumentRanges(self._s[1074]!, self._r[1074]!, [_0]) } - public var StickerPacksSettings_Title: String { return self._s[1070]! } - public var Conversation_MessageDialogDelete: String { return self._s[1071]! } - public var Privacy_Calls_CustomHelp: String { return self._s[1073]! } - public var Message_Wallpaper: String { return self._s[1074]! } - public var MemberSearch_BotSection: String { return self._s[1075]! } - public var GroupInfo_SetSound: String { return self._s[1076]! } + public var StickerPacksSettings_Title: String { return self._s[1075]! } + public var Conversation_MessageDialogDelete: String { return self._s[1076]! } + public var Privacy_Calls_CustomHelp: String { return self._s[1078]! } + public var Message_Wallpaper: String { return self._s[1079]! } + public var MemberSearch_BotSection: String { return self._s[1080]! } + public var GroupInfo_SetSound: String { return self._s[1081]! } public func Time_TomorrowAt(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1077]!, self._r[1077]!, [_0]) + return formatWithArgumentRanges(self._s[1082]!, self._r[1082]!, [_0]) } - public var Core_ServiceUserStatus: String { return self._s[1078]! } - public var LiveLocationUpdated_JustNow: String { return self._s[1079]! } - public var Call_StatusFailed: String { return self._s[1080]! } - public var TwoFactorSetup_Email_Placeholder: String { return self._s[1081]! } - public var TwoStepAuth_SetupPasswordDescription: String { return self._s[1082]! } - public var TwoStepAuth_SetPassword: String { return self._s[1083]! } - public var Permissions_PeopleNearbyText_v0: String { return self._s[1084]! } + public var Core_ServiceUserStatus: String { return self._s[1083]! } + public var LiveLocationUpdated_JustNow: String { return self._s[1084]! } + public var Call_StatusFailed: String { return self._s[1085]! } + public var TwoFactorSetup_Email_Placeholder: String { return self._s[1086]! } + public var TwoStepAuth_SetupPasswordDescription: String { return self._s[1087]! } + public var TwoStepAuth_SetPassword: String { return self._s[1088]! } + public var Permissions_PeopleNearbyText_v0: String { return self._s[1089]! } public func SocksProxySetup_ProxyStatusPing(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1086]!, self._r[1086]!, [_0]) + return formatWithArgumentRanges(self._s[1091]!, self._r[1091]!, [_0]) } - public var Calls_SubmitRating: String { return self._s[1087]! } - public var Map_NoPlacesNearby: String { return self._s[1088]! } - public var Profile_Username: String { return self._s[1089]! } - public var Bot_DescriptionTitle: String { return self._s[1090]! } - public var MaskStickerSettings_Title: String { return self._s[1091]! } - public var SharedMedia_CategoryOther: String { return self._s[1092]! } - public var GroupInfo_SetGroupPhoto: String { return self._s[1093]! } - public var Common_NotNow: String { return self._s[1094]! } - public var CallFeedback_IncludeLogsInfo: String { return self._s[1095]! } - public var Conversation_ShareMyPhoneNumber: String { return self._s[1096]! } - public var Map_Location: String { return self._s[1097]! } - public var Invitation_JoinGroup: String { return self._s[1098]! } - public var AutoDownloadSettings_Title: String { return self._s[1100]! } - public var Conversation_DiscardVoiceMessageDescription: String { return self._s[1101]! } - public var Channel_ErrorAddBlocked: String { return self._s[1102]! } - public var Conversation_UnblockUser: String { return self._s[1103]! } - public var EditTheme_Edit_TopInfo: String { return self._s[1104]! } - public var Watch_Bot_Restart: String { return self._s[1105]! } - public var TwoStepAuth_Title: String { return self._s[1106]! } - public var Channel_AdminLog_BanSendMessages: String { return self._s[1107]! } - public var Checkout_ShippingMethod: String { return self._s[1108]! } - public var Passport_Identity_OneOfTypeIdentityCard: String { return self._s[1109]! } + public var Calls_SubmitRating: String { return self._s[1092]! } + public var Map_NoPlacesNearby: String { return self._s[1093]! } + public var Profile_Username: String { return self._s[1094]! } + public var Bot_DescriptionTitle: String { return self._s[1095]! } + public var MaskStickerSettings_Title: String { return self._s[1096]! } + public var SharedMedia_CategoryOther: String { return self._s[1097]! } + public var GroupInfo_SetGroupPhoto: String { return self._s[1098]! } + public var Common_NotNow: String { return self._s[1099]! } + public var CallFeedback_IncludeLogsInfo: String { return self._s[1100]! } + public var Conversation_ShareMyPhoneNumber: String { return self._s[1101]! } + public var Map_Location: String { return self._s[1102]! } + public var Invitation_JoinGroup: String { return self._s[1103]! } + public var AutoDownloadSettings_Title: String { return self._s[1105]! } + public var Conversation_DiscardVoiceMessageDescription: String { return self._s[1106]! } + public var Channel_ErrorAddBlocked: String { return self._s[1107]! } + public var Conversation_UnblockUser: String { return self._s[1108]! } + public var EditTheme_Edit_TopInfo: String { return self._s[1109]! } + public var Watch_Bot_Restart: String { return self._s[1110]! } + public var TwoStepAuth_Title: String { return self._s[1111]! } + public var Channel_AdminLog_BanSendMessages: String { return self._s[1112]! } + public var Checkout_ShippingMethod: String { return self._s[1113]! } + public var Passport_Identity_OneOfTypeIdentityCard: String { return self._s[1114]! } public func PUSH_CHAT_MESSAGE_STICKER(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1110]!, self._r[1110]!, [_1, _2, _3]) + return formatWithArgumentRanges(self._s[1115]!, self._r[1115]!, [_1, _2, _3]) } - public var PeerInfo_ButtonDiscuss: String { return self._s[1111]! } - public var EditTheme_ChangeColors: String { return self._s[1113]! } + public var PeerInfo_ButtonDiscuss: String { return self._s[1116]! } + public var EditTheme_ChangeColors: String { return self._s[1118]! } public func Chat_UnsendMyMessagesAlertTitle(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1114]!, self._r[1114]!, [_0]) + return formatWithArgumentRanges(self._s[1119]!, self._r[1119]!, [_0]) } public func Channel_Username_LinkHint(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1115]!, self._r[1115]!, [_0]) + return formatWithArgumentRanges(self._s[1120]!, self._r[1120]!, [_0]) } - public var Appearance_ThemePreview_ChatList_1_Name: String { return self._s[1116]! } - public var SettingsSearch_Synonyms_Data_AutoplayGifs: String { return self._s[1117]! } - public var AuthSessions_TerminateOtherSessions: String { return self._s[1118]! } - public var Contacts_FailedToSendInvitesMessage: String { return self._s[1119]! } - public var PrivacySettings_TwoStepAuth: String { return self._s[1120]! } - public var Notification_Exceptions_PreviewAlwaysOn: String { return self._s[1121]! } - public var SettingsSearch_Synonyms_Privacy_Passcode: String { return self._s[1122]! } - public var Conversation_EditingMessagePanelMedia: String { return self._s[1123]! } - public var Checkout_PaymentMethod_Title: String { return self._s[1124]! } - public var SocksProxySetup_Connection: String { return self._s[1125]! } - public var Group_MessagePhotoRemoved: String { return self._s[1126]! } - public var PeopleNearby_MakeInvisible: String { return self._s[1128]! } - public var Channel_Stickers_NotFound: String { return self._s[1130]! } - public var Group_About_Help: String { return self._s[1131]! } - public var Notification_PassportValueProofOfIdentity: String { return self._s[1132]! } - public var PeopleNearby_Title: String { return self._s[1134]! } + public var Appearance_ThemePreview_ChatList_1_Name: String { return self._s[1121]! } + public var SettingsSearch_Synonyms_Data_AutoplayGifs: String { return self._s[1122]! } + public var AuthSessions_TerminateOtherSessions: String { return self._s[1123]! } + public var Contacts_FailedToSendInvitesMessage: String { return self._s[1124]! } + public var PrivacySettings_TwoStepAuth: String { return self._s[1125]! } + public var Notification_Exceptions_PreviewAlwaysOn: String { return self._s[1126]! } + public var SettingsSearch_Synonyms_Privacy_Passcode: String { return self._s[1127]! } + public var Conversation_EditingMessagePanelMedia: String { return self._s[1128]! } + public var Checkout_PaymentMethod_Title: String { return self._s[1129]! } + public var SocksProxySetup_Connection: String { return self._s[1130]! } + public var Group_MessagePhotoRemoved: String { return self._s[1131]! } + public var PeopleNearby_MakeInvisible: String { return self._s[1133]! } + public var Channel_Stickers_NotFound: String { return self._s[1135]! } + public var Group_About_Help: String { return self._s[1136]! } + public var Notification_PassportValueProofOfIdentity: String { return self._s[1137]! } + public var PeopleNearby_Title: String { return self._s[1139]! } public func ApplyLanguage_ChangeLanguageOfficialText(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1135]!, self._r[1135]!, [_1]) + return formatWithArgumentRanges(self._s[1140]!, self._r[1140]!, [_1]) } - public var Map_Home: String { return self._s[1136]! } - public var CheckoutInfo_ShippingInfoStatePlaceholder: String { return self._s[1138]! } - public var Notifications_GroupNotificationsExceptionsHelp: String { return self._s[1139]! } - public var SocksProxySetup_Password: String { return self._s[1140]! } - public var Notifications_PermissionsEnable: String { return self._s[1141]! } - public var TwoStepAuth_ChangeEmail: String { return self._s[1143]! } + public var Map_Home: String { return self._s[1141]! } + public var CheckoutInfo_ShippingInfoStatePlaceholder: String { return self._s[1143]! } + public var Notifications_GroupNotificationsExceptionsHelp: String { return self._s[1144]! } + public var SocksProxySetup_Password: String { return self._s[1145]! } + public var Notifications_PermissionsEnable: String { return self._s[1146]! } + public var TwoStepAuth_ChangeEmail: String { return self._s[1148]! } public func Channel_AdminLog_MessageInvitedName(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1144]!, self._r[1144]!, [_1]) + return formatWithArgumentRanges(self._s[1149]!, self._r[1149]!, [_1]) } public func Time_MonthOfYear_m10(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1146]!, self._r[1146]!, [_0]) + return formatWithArgumentRanges(self._s[1151]!, self._r[1151]!, [_0]) } - public var Passport_Identity_TypeDriversLicense: String { return self._s[1147]! } - public var ArchivedPacksAlert_Title: String { return self._s[1148]! } - public var Wallet_Receive_InvoiceUrlCopied: String { return self._s[1149]! } - public var Map_PlacesNearby: String { return self._s[1150]! } + public var Passport_Identity_TypeDriversLicense: String { return self._s[1152]! } + public var ArchivedPacksAlert_Title: String { return self._s[1153]! } + public var Wallet_Receive_InvoiceUrlCopied: String { return self._s[1154]! } + public var Map_PlacesNearby: String { return self._s[1155]! } public func Time_PreciseDate_m7(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1151]!, self._r[1151]!, [_1, _2, _3]) + return formatWithArgumentRanges(self._s[1156]!, self._r[1156]!, [_1, _2, _3]) } - public var PrivacyLastSeenSettings_GroupsAndChannelsHelp: String { return self._s[1152]! } - public var Privacy_Calls_NeverAllow_Placeholder: String { return self._s[1154]! } - public var Conversation_StatusTyping: String { return self._s[1155]! } - public var Broadcast_AdminLog_EmptyText: String { return self._s[1156]! } - public var Notification_PassportValueProofOfAddress: String { return self._s[1157]! } - public var UserInfo_CreateNewContact: String { return self._s[1158]! } - public var Passport_Identity_FrontSide: String { return self._s[1159]! } - public var Login_PhoneNumberAlreadyAuthorizedSwitch: String { return self._s[1160]! } - public var Calls_CallTabTitle: String { return self._s[1161]! } - public var Channel_AdminLog_ChannelEmptyText: String { return self._s[1162]! } + public var PrivacyLastSeenSettings_GroupsAndChannelsHelp: String { return self._s[1157]! } + public var Privacy_Calls_NeverAllow_Placeholder: String { return self._s[1159]! } + public var Conversation_StatusTyping: String { return self._s[1160]! } + public var Broadcast_AdminLog_EmptyText: String { return self._s[1161]! } + public var Notification_PassportValueProofOfAddress: String { return self._s[1162]! } + public var UserInfo_CreateNewContact: String { return self._s[1163]! } + public var Passport_Identity_FrontSide: String { return self._s[1164]! } + public var Login_PhoneNumberAlreadyAuthorizedSwitch: String { return self._s[1165]! } + public var Calls_CallTabTitle: String { return self._s[1166]! } + public var Channel_AdminLog_ChannelEmptyText: String { return self._s[1167]! } public func Login_BannedPhoneBody(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1164]!, self._r[1164]!, [_0]) + return formatWithArgumentRanges(self._s[1169]!, self._r[1169]!, [_0]) } - public var Watch_UserInfo_MuteTitle: String { return self._s[1165]! } - public var Group_EditAdmin_RankAdminPlaceholder: String { return self._s[1166]! } - public var SharedMedia_EmptyMusicText: String { return self._s[1167]! } - public var Wallet_Completed_Text: String { return self._s[1168]! } - public var PasscodeSettings_AutoLock_IfAwayFor_1minute: String { return self._s[1169]! } - public var Paint_Stickers: String { return self._s[1170]! } - public var Privacy_GroupsAndChannels: String { return self._s[1171]! } - public var ChatList_Context_Delete: String { return self._s[1173]! } - public var UserInfo_AddContact: String { return self._s[1174]! } + public var Watch_UserInfo_MuteTitle: String { return self._s[1170]! } + public var Group_EditAdmin_RankAdminPlaceholder: String { return self._s[1171]! } + public var SharedMedia_EmptyMusicText: String { return self._s[1172]! } + public var Wallet_Completed_Text: String { return self._s[1173]! } + public var PasscodeSettings_AutoLock_IfAwayFor_1minute: String { return self._s[1174]! } + public var Paint_Stickers: String { return self._s[1175]! } + public var Privacy_GroupsAndChannels: String { return self._s[1176]! } + public var ChatList_Context_Delete: String { return self._s[1178]! } + public var UserInfo_AddContact: String { return self._s[1179]! } public func Conversation_MessageViaUser(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1175]!, self._r[1175]!, [_0]) + return formatWithArgumentRanges(self._s[1180]!, self._r[1180]!, [_0]) } - public var PhoneNumberHelp_ChangeNumber: String { return self._s[1177]! } + public var PhoneNumberHelp_ChangeNumber: String { return self._s[1182]! } public func ChatList_ClearChatConfirmation(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1179]!, self._r[1179]!, [_0]) + return formatWithArgumentRanges(self._s[1184]!, self._r[1184]!, [_0]) } - public var DialogList_NoMessagesTitle: String { return self._s[1180]! } - public var EditProfile_NameAndPhotoHelp: String { return self._s[1181]! } - public var BlockedUsers_BlockUser: String { return self._s[1182]! } - public var Notifications_PermissionsOpenSettings: String { return self._s[1183]! } - public var MediaPicker_UngroupDescription: String { return self._s[1185]! } - public var Watch_NoConnection: String { return self._s[1186]! } - public var Month_GenSeptember: String { return self._s[1187]! } - public var Conversation_ViewGroup: String { return self._s[1189]! } - public var Channel_AdminLogFilter_EventsLeavingSubscribers: String { return self._s[1192]! } - public var Privacy_Forwards_AlwaysLink: String { return self._s[1193]! } - public var Channel_OwnershipTransfer_ErrorAdminsTooMuch: String { return self._s[1194]! } - public var Passport_FieldOneOf_FinalDelimeter: String { return self._s[1195]! } - public var Wallet_WordCheck_IncorrectHeader: String { return self._s[1196]! } - public var MediaPicker_CameraRoll: String { return self._s[1198]! } - public var Month_GenAugust: String { return self._s[1199]! } - public var Wallet_Configuration_SourceHeader: String { return self._s[1200]! } - public var AccessDenied_VideoMessageMicrophone: String { return self._s[1201]! } - public var SharedMedia_EmptyText: String { return self._s[1202]! } - public var Map_ShareLiveLocation: String { return self._s[1203]! } - public var Calls_All: String { return self._s[1204]! } - public var Map_SendThisPlace: String { return self._s[1206]! } - public var Appearance_ThemeNight: String { return self._s[1208]! } - public var Conversation_HoldForAudio: String { return self._s[1209]! } - public var SettingsSearch_Synonyms_Support: String { return self._s[1212]! } - public var GroupInfo_GroupHistoryHidden: String { return self._s[1213]! } - public var SocksProxySetup_Secret: String { return self._s[1214]! } + public var DialogList_NoMessagesTitle: String { return self._s[1185]! } + public var EditProfile_NameAndPhotoHelp: String { return self._s[1186]! } + public var BlockedUsers_BlockUser: String { return self._s[1187]! } + public var Notifications_PermissionsOpenSettings: String { return self._s[1188]! } + public var MediaPicker_UngroupDescription: String { return self._s[1190]! } + public var Watch_NoConnection: String { return self._s[1191]! } + public var Month_GenSeptember: String { return self._s[1192]! } + public var Conversation_ViewGroup: String { return self._s[1194]! } + public var Channel_AdminLogFilter_EventsLeavingSubscribers: String { return self._s[1197]! } + public var Privacy_Forwards_AlwaysLink: String { return self._s[1198]! } + public var Channel_OwnershipTransfer_ErrorAdminsTooMuch: String { return self._s[1199]! } + public var Passport_FieldOneOf_FinalDelimeter: String { return self._s[1200]! } + public var Wallet_WordCheck_IncorrectHeader: String { return self._s[1201]! } + public var MediaPicker_CameraRoll: String { return self._s[1203]! } + public var Month_GenAugust: String { return self._s[1204]! } + public var Wallet_Configuration_SourceHeader: String { return self._s[1205]! } + public var AccessDenied_VideoMessageMicrophone: String { return self._s[1206]! } + public var SharedMedia_EmptyText: String { return self._s[1207]! } + public var Map_ShareLiveLocation: String { return self._s[1208]! } + public var Calls_All: String { return self._s[1209]! } + public var Map_SendThisPlace: String { return self._s[1211]! } + public var Appearance_ThemeNight: String { return self._s[1213]! } + public var Conversation_HoldForAudio: String { return self._s[1214]! } + public var SettingsSearch_Synonyms_Support: String { return self._s[1217]! } + public var GroupInfo_GroupHistoryHidden: String { return self._s[1218]! } + public var SocksProxySetup_Secret: String { return self._s[1219]! } public func Activity_RemindAboutChannel(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1215]!, self._r[1215]!, [_0]) + return formatWithArgumentRanges(self._s[1220]!, self._r[1220]!, [_0]) } - public var Channel_BanList_RestrictedTitle: String { return self._s[1217]! } - public var Conversation_Location: String { return self._s[1218]! } + public var Channel_BanList_RestrictedTitle: String { return self._s[1222]! } + public var Conversation_Location: String { return self._s[1223]! } public func AutoDownloadSettings_UpToFor(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1219]!, self._r[1219]!, [_1, _2]) + return formatWithArgumentRanges(self._s[1224]!, self._r[1224]!, [_1, _2]) } - public var ChatSettings_AutoDownloadPhotos: String { return self._s[1221]! } - public var SettingsSearch_Synonyms_Privacy_Title: String { return self._s[1222]! } - public var Notifications_PermissionsText: String { return self._s[1223]! } - public var SettingsSearch_Synonyms_Data_SaveIncomingPhotos: String { return self._s[1224]! } - public var Call_Flip: String { return self._s[1225]! } - public var Channel_AdminLog_CanDeleteMessagesOfOthers: String { return self._s[1227]! } - public var SocksProxySetup_ProxyStatusConnecting: String { return self._s[1228]! } - public var Wallet_TransactionInfo_StorageFeeInfoUrl: String { return self._s[1229]! } - public var PrivacyPhoneNumberSettings_DiscoveryHeader: String { return self._s[1230]! } - public var Channel_EditAdmin_PermissionPinMessages: String { return self._s[1232]! } - public var TwoStepAuth_ReEnterPasswordDescription: String { return self._s[1234]! } - public var Channel_TooMuchBots: String { return self._s[1236]! } - public var Passport_DeletePassportConfirmation: String { return self._s[1237]! } - public var Login_InvalidCodeError: String { return self._s[1238]! } - public var StickerPacksSettings_FeaturedPacks: String { return self._s[1239]! } + public var ChatSettings_AutoDownloadPhotos: String { return self._s[1226]! } + public var SettingsSearch_Synonyms_Privacy_Title: String { return self._s[1227]! } + public var Notifications_PermissionsText: String { return self._s[1228]! } + public var SettingsSearch_Synonyms_Data_SaveIncomingPhotos: String { return self._s[1229]! } + public var Call_Flip: String { return self._s[1230]! } + public var Channel_AdminLog_CanDeleteMessagesOfOthers: String { return self._s[1232]! } + public var SocksProxySetup_ProxyStatusConnecting: String { return self._s[1233]! } + public var Wallet_TransactionInfo_StorageFeeInfoUrl: String { return self._s[1234]! } + public var PrivacyPhoneNumberSettings_DiscoveryHeader: String { return self._s[1235]! } + public var Channel_EditAdmin_PermissionPinMessages: String { return self._s[1237]! } + public var TwoStepAuth_ReEnterPasswordDescription: String { return self._s[1239]! } + public var Channel_TooMuchBots: String { return self._s[1241]! } + public var Passport_DeletePassportConfirmation: String { return self._s[1242]! } + public var Login_InvalidCodeError: String { return self._s[1243]! } + public var StickerPacksSettings_FeaturedPacks: String { return self._s[1244]! } public func ChatList_DeleteSecretChatConfirmation(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1240]!, self._r[1240]!, [_0]) + return formatWithArgumentRanges(self._s[1245]!, self._r[1245]!, [_0]) } public func GroupInfo_InvitationLinkAcceptChannel(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1241]!, self._r[1241]!, [_0]) + return formatWithArgumentRanges(self._s[1246]!, self._r[1246]!, [_0]) } - public var VoiceOver_Navigation_ProxySettings: String { return self._s[1242]! } - public var Call_CallInProgressTitle: String { return self._s[1243]! } - public var Month_ShortSeptember: String { return self._s[1244]! } - public var Watch_ChannelInfo_Title: String { return self._s[1245]! } - public var ChatList_DeleteSavedMessagesConfirmation: String { return self._s[1248]! } - public var DialogList_PasscodeLockHelp: String { return self._s[1249]! } - public var Chat_MultipleTextMessagesDisabled: String { return self._s[1250]! } - public var Wallet_Receive_Title: String { return self._s[1251]! } - public var Notifications_Badge_IncludePublicGroups: String { return self._s[1252]! } - public var Channel_AdminLogFilter_EventsTitle: String { return self._s[1253]! } - public var PhotoEditor_CropReset: String { return self._s[1254]! } - public var Group_Username_CreatePrivateLinkHelp: String { return self._s[1256]! } - public var Channel_Management_LabelEditor: String { return self._s[1257]! } - public var Passport_Identity_LatinNameHelp: String { return self._s[1259]! } - public var PhotoEditor_HighlightsTool: String { return self._s[1260]! } - public var Wallet_Info_WalletCreated: String { return self._s[1261]! } - public var UserInfo_Title: String { return self._s[1262]! } - public var ChatList_HideAction: String { return self._s[1263]! } - public var AccessDenied_Title: String { return self._s[1264]! } - public var DialogList_SearchLabel: String { return self._s[1265]! } - public var Group_Setup_HistoryHidden: String { return self._s[1266]! } - public var TwoStepAuth_PasswordChangeSuccess: String { return self._s[1267]! } - public var State_Updating: String { return self._s[1269]! } - public var Contacts_TabTitle: String { return self._s[1270]! } - public var Notifications_Badge_CountUnreadMessages: String { return self._s[1272]! } - public var GroupInfo_GroupHistory: String { return self._s[1273]! } - public var Conversation_UnsupportedMediaPlaceholder: String { return self._s[1274]! } - public var Wallpaper_SetColor: String { return self._s[1275]! } - public var CheckoutInfo_ShippingInfoCountry: String { return self._s[1276]! } - public var SettingsSearch_Synonyms_SavedMessages: String { return self._s[1277]! } - public var Chat_AttachmentLimitReached: String { return self._s[1278]! } - public var Passport_Identity_OneOfTypeDriversLicense: String { return self._s[1279]! } - public var Contacts_NotRegisteredSection: String { return self._s[1280]! } + public var VoiceOver_Navigation_ProxySettings: String { return self._s[1247]! } + public var Call_CallInProgressTitle: String { return self._s[1248]! } + public var Month_ShortSeptember: String { return self._s[1249]! } + public var Watch_ChannelInfo_Title: String { return self._s[1250]! } + public var ChatList_DeleteSavedMessagesConfirmation: String { return self._s[1253]! } + public var DialogList_PasscodeLockHelp: String { return self._s[1254]! } + public var Chat_MultipleTextMessagesDisabled: String { return self._s[1255]! } + public var Wallet_Receive_Title: String { return self._s[1256]! } + public var Notifications_Badge_IncludePublicGroups: String { return self._s[1257]! } + public var Channel_AdminLogFilter_EventsTitle: String { return self._s[1258]! } + public var PhotoEditor_CropReset: String { return self._s[1259]! } + public var Group_Username_CreatePrivateLinkHelp: String { return self._s[1261]! } + public var Channel_Management_LabelEditor: String { return self._s[1262]! } + public var Passport_Identity_LatinNameHelp: String { return self._s[1264]! } + public var PhotoEditor_HighlightsTool: String { return self._s[1265]! } + public var Wallet_Info_WalletCreated: String { return self._s[1266]! } + public var UserInfo_Title: String { return self._s[1267]! } + public var ChatList_HideAction: String { return self._s[1268]! } + public var AccessDenied_Title: String { return self._s[1269]! } + public var DialogList_SearchLabel: String { return self._s[1270]! } + public var Group_Setup_HistoryHidden: String { return self._s[1271]! } + public var TwoStepAuth_PasswordChangeSuccess: String { return self._s[1272]! } + public var State_Updating: String { return self._s[1274]! } + public var Contacts_TabTitle: String { return self._s[1275]! } + public var Notifications_Badge_CountUnreadMessages: String { return self._s[1277]! } + public var GroupInfo_GroupHistory: String { return self._s[1278]! } + public var Conversation_UnsupportedMediaPlaceholder: String { return self._s[1279]! } + public var Wallpaper_SetColor: String { return self._s[1280]! } + public var CheckoutInfo_ShippingInfoCountry: String { return self._s[1281]! } + public var SettingsSearch_Synonyms_SavedMessages: String { return self._s[1282]! } + public var Chat_AttachmentLimitReached: String { return self._s[1283]! } + public var Passport_Identity_OneOfTypeDriversLicense: String { return self._s[1284]! } + public var Contacts_NotRegisteredSection: String { return self._s[1285]! } public func Time_PreciseDate_m4(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1281]!, self._r[1281]!, [_1, _2, _3]) + return formatWithArgumentRanges(self._s[1286]!, self._r[1286]!, [_1, _2, _3]) } - public var Paint_Clear: String { return self._s[1282]! } - public var StickerPacksSettings_ArchivedMasks: String { return self._s[1283]! } - public var SocksProxySetup_Connecting: String { return self._s[1284]! } - public var ExplicitContent_AlertChannel: String { return self._s[1285]! } - public var CreatePoll_AllOptionsAdded: String { return self._s[1286]! } - public var Conversation_Contact: String { return self._s[1287]! } - public var Login_CodeExpired: String { return self._s[1288]! } - public var Passport_DiscardMessageAction: String { return self._s[1289]! } - public var ChatList_Context_Unpin: String { return self._s[1290]! } - public var Channel_AdminLog_MessagePreviousDescription: String { return self._s[1291]! } + public var Paint_Clear: String { return self._s[1287]! } + public var StickerPacksSettings_ArchivedMasks: String { return self._s[1288]! } + public var SocksProxySetup_Connecting: String { return self._s[1289]! } + public var ExplicitContent_AlertChannel: String { return self._s[1290]! } + public var CreatePoll_AllOptionsAdded: String { return self._s[1291]! } + public var Conversation_Contact: String { return self._s[1292]! } + public var Login_CodeExpired: String { return self._s[1293]! } + public var Passport_DiscardMessageAction: String { return self._s[1294]! } + public var ChatList_Context_Unpin: String { return self._s[1295]! } + public var Channel_AdminLog_MessagePreviousDescription: String { return self._s[1296]! } public func VoiceOver_Chat_MusicFrom(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1292]!, self._r[1292]!, [_0]) + return formatWithArgumentRanges(self._s[1297]!, self._r[1297]!, [_0]) } - public var Channel_AdminLog_EmptyMessageText: String { return self._s[1293]! } - public var SettingsSearch_Synonyms_Data_NetworkUsage: String { return self._s[1294]! } + public var Channel_AdminLog_EmptyMessageText: String { return self._s[1298]! } + public var SettingsSearch_Synonyms_Data_NetworkUsage: String { return self._s[1299]! } public func Group_EditAdmin_RankInfo(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1295]!, self._r[1295]!, [_0]) + return formatWithArgumentRanges(self._s[1300]!, self._r[1300]!, [_0]) } - public var Month_ShortApril: String { return self._s[1296]! } - public var AuthSessions_CurrentSession: String { return self._s[1297]! } - public var Chat_AttachmentMultipleFilesDisabled: String { return self._s[1300]! } - public var Wallet_Navigation_Cancel: String { return self._s[1302]! } - public var WallpaperPreview_CropTopText: String { return self._s[1303]! } - public var PrivacySettings_DeleteAccountIfAwayFor: String { return self._s[1304]! } - public var CheckoutInfo_ShippingInfoTitle: String { return self._s[1305]! } + public var Month_ShortApril: String { return self._s[1301]! } + public var AuthSessions_CurrentSession: String { return self._s[1302]! } + public var Chat_AttachmentMultipleFilesDisabled: String { return self._s[1305]! } + public var Wallet_Navigation_Cancel: String { return self._s[1307]! } + public var WallpaperPreview_CropTopText: String { return self._s[1308]! } + public var PrivacySettings_DeleteAccountIfAwayFor: String { return self._s[1309]! } + public var CheckoutInfo_ShippingInfoTitle: String { return self._s[1310]! } public func Conversation_ScheduleMessage_SendOn(_ _0: String, _ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1306]!, self._r[1306]!, [_0, _1]) + return formatWithArgumentRanges(self._s[1311]!, self._r[1311]!, [_0, _1]) } - public var Appearance_ThemePreview_Chat_2_Text: String { return self._s[1307]! } - public var Channel_Setup_TypePrivate: String { return self._s[1309]! } - public var Forward_ChannelReadOnly: String { return self._s[1312]! } - public var PhotoEditor_CurvesBlue: String { return self._s[1313]! } - public var AddContact_SharedContactException: String { return self._s[1314]! } - public var UserInfo_BotPrivacy: String { return self._s[1316]! } - public var Wallet_CreateInvoice_Title: String { return self._s[1317]! } - public var Notification_PassportValueEmail: String { return self._s[1318]! } - public var EmptyGroupInfo_Subtitle: String { return self._s[1319]! } - public var GroupPermission_NewTitle: String { return self._s[1320]! } - public var CallFeedback_ReasonDropped: String { return self._s[1321]! } - public var GroupInfo_Permissions_AddException: String { return self._s[1322]! } - public var Channel_SignMessages_Help: String { return self._s[1324]! } - public var Undo_ChatDeleted: String { return self._s[1326]! } - public var Conversation_ChatBackground: String { return self._s[1327]! } + public var Appearance_ThemePreview_Chat_2_Text: String { return self._s[1312]! } + public var Channel_Setup_TypePrivate: String { return self._s[1314]! } + public var Forward_ChannelReadOnly: String { return self._s[1317]! } + public var PhotoEditor_CurvesBlue: String { return self._s[1318]! } + public var AddContact_SharedContactException: String { return self._s[1319]! } + public var UserInfo_BotPrivacy: String { return self._s[1321]! } + public var Wallet_CreateInvoice_Title: String { return self._s[1322]! } + public var Notification_PassportValueEmail: String { return self._s[1323]! } + public var EmptyGroupInfo_Subtitle: String { return self._s[1324]! } + public var GroupPermission_NewTitle: String { return self._s[1325]! } + public var CallFeedback_ReasonDropped: String { return self._s[1326]! } + public var GroupInfo_Permissions_AddException: String { return self._s[1327]! } + public var Channel_SignMessages_Help: String { return self._s[1329]! } + public var Undo_ChatDeleted: String { return self._s[1331]! } + public var Conversation_ChatBackground: String { return self._s[1332]! } public func Wallet_WordCheck_Text(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1328]!, self._r[1328]!, [_1, _2, _3]) + return formatWithArgumentRanges(self._s[1333]!, self._r[1333]!, [_1, _2, _3]) } public func PUSH_CHAT_MESSAGE_QUIZ(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1329]!, self._r[1329]!, [_1, _2, _3]) + return formatWithArgumentRanges(self._s[1334]!, self._r[1334]!, [_1, _2, _3]) } - public var ChannelMembers_WhoCanAddMembers_Admins: String { return self._s[1330]! } - public var FastTwoStepSetup_EmailPlaceholder: String { return self._s[1331]! } - public var Passport_Language_pt: String { return self._s[1332]! } - public var VoiceOver_Chat_YourVoiceMessage: String { return self._s[1333]! } - public var NotificationsSound_Popcorn: String { return self._s[1336]! } - public var AutoNightTheme_Disabled: String { return self._s[1337]! } - public var BlockedUsers_LeavePrefix: String { return self._s[1338]! } - public var WallpaperPreview_CustomColorTopText: String { return self._s[1339]! } - public var Contacts_PermissionsSuppressWarningText: String { return self._s[1340]! } - public var WallpaperSearch_ColorBlue: String { return self._s[1341]! } + public var ChannelMembers_WhoCanAddMembers_Admins: String { return self._s[1335]! } + public var FastTwoStepSetup_EmailPlaceholder: String { return self._s[1336]! } + public var Passport_Language_pt: String { return self._s[1337]! } + public var VoiceOver_Chat_YourVoiceMessage: String { return self._s[1338]! } + public var NotificationsSound_Popcorn: String { return self._s[1341]! } + public var AutoNightTheme_Disabled: String { return self._s[1342]! } + public var BlockedUsers_LeavePrefix: String { return self._s[1343]! } + public var WallpaperPreview_CustomColorTopText: String { return self._s[1344]! } + public var Contacts_PermissionsSuppressWarningText: String { return self._s[1345]! } + public var WallpaperSearch_ColorBlue: String { return self._s[1346]! } public func CancelResetAccount_TextSMS(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1342]!, self._r[1342]!, [_0]) + return formatWithArgumentRanges(self._s[1347]!, self._r[1347]!, [_0]) } - public var CheckoutInfo_ErrorNameInvalid: String { return self._s[1343]! } - public var SocksProxySetup_UseForCalls: String { return self._s[1344]! } - public var Passport_DeleteDocumentConfirmation: String { return self._s[1346]! } - public var PeerInfo_PaneGroups: String { return self._s[1347]! } + public var CheckoutInfo_ErrorNameInvalid: String { return self._s[1348]! } + public var SocksProxySetup_UseForCalls: String { return self._s[1349]! } + public var Passport_DeleteDocumentConfirmation: String { return self._s[1351]! } + public var PeerInfo_PaneGroups: String { return self._s[1352]! } public func Conversation_Megabytes(_ _0: Float) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1348]!, self._r[1348]!, ["\(_0)"]) + return formatWithArgumentRanges(self._s[1353]!, self._r[1353]!, ["\(_0)"]) } - public var SocksProxySetup_Hostname: String { return self._s[1351]! } - public var ChatSettings_AutoDownloadSettings_OffForAll: String { return self._s[1352]! } - public var Compose_NewEncryptedChat: String { return self._s[1353]! } - public var Login_CodeFloodError: String { return self._s[1354]! } - public var Calls_TabTitle: String { return self._s[1355]! } - public var Privacy_ProfilePhoto: String { return self._s[1356]! } - public var Passport_Language_he: String { return self._s[1357]! } + public var SocksProxySetup_Hostname: String { return self._s[1356]! } + public var ChatSettings_AutoDownloadSettings_OffForAll: String { return self._s[1357]! } + public var Compose_NewEncryptedChat: String { return self._s[1358]! } + public var Login_CodeFloodError: String { return self._s[1359]! } + public var Calls_TabTitle: String { return self._s[1360]! } + public var Privacy_ProfilePhoto: String { return self._s[1361]! } + public var Passport_Language_he: String { return self._s[1362]! } public func Conversation_SetReminder_RemindToday(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1358]!, self._r[1358]!, [_0]) + return formatWithArgumentRanges(self._s[1363]!, self._r[1363]!, [_0]) } - public var GroupPermission_Title: String { return self._s[1359]! } + public var GroupPermission_Title: String { return self._s[1364]! } public func Channel_AdminLog_MessageGroupPreHistoryHidden(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1360]!, self._r[1360]!, [_0]) + return formatWithArgumentRanges(self._s[1365]!, self._r[1365]!, [_0]) } - public var Wallet_TransactionInfo_SenderHeader: String { return self._s[1361]! } - public var GroupPermission_NoChangeInfo: String { return self._s[1362]! } - public var ChatList_DeleteForCurrentUser: String { return self._s[1363]! } - public var Tour_Text1: String { return self._s[1364]! } - public var Channel_EditAdmin_TransferOwnership: String { return self._s[1365]! } - public var Month_ShortFebruary: String { return self._s[1366]! } - public var TwoStepAuth_EmailSkip: String { return self._s[1367]! } + public var Wallet_TransactionInfo_SenderHeader: String { return self._s[1366]! } + public var GroupPermission_NoChangeInfo: String { return self._s[1367]! } + public var ChatList_DeleteForCurrentUser: String { return self._s[1368]! } + public var Tour_Text1: String { return self._s[1369]! } + public var Channel_EditAdmin_TransferOwnership: String { return self._s[1370]! } + public var Month_ShortFebruary: String { return self._s[1371]! } + public var TwoStepAuth_EmailSkip: String { return self._s[1372]! } public func Wallet_Time_PreciseDate_m4(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1368]!, self._r[1368]!, [_1, _2, _3]) + return formatWithArgumentRanges(self._s[1373]!, self._r[1373]!, [_1, _2, _3]) } - public var NotificationsSound_Glass: String { return self._s[1369]! } - public var Appearance_ThemeNightBlue: String { return self._s[1370]! } - public var CheckoutInfo_Pay: String { return self._s[1371]! } - public var PeerInfo_ButtonLeave: String { return self._s[1373]! } - public var Invite_LargeRecipientsCountWarning: String { return self._s[1374]! } - public var Call_CallAgain: String { return self._s[1376]! } - public var AttachmentMenu_SendAsFile: String { return self._s[1377]! } - public var AccessDenied_MicrophoneRestricted: String { return self._s[1378]! } - public var Passport_InvalidPasswordError: String { return self._s[1379]! } - public var Watch_Message_Game: String { return self._s[1380]! } - public var Stickers_Install: String { return self._s[1381]! } - public var VoiceOver_Chat_Message: String { return self._s[1382]! } - public var PrivacyLastSeenSettings_NeverShareWith: String { return self._s[1383]! } - public var Passport_Identity_ResidenceCountry: String { return self._s[1385]! } - public var Notifications_GroupNotificationsHelp: String { return self._s[1386]! } - public var AuthSessions_OtherSessions: String { return self._s[1387]! } - public var Channel_Username_Help: String { return self._s[1388]! } - public var Camera_Title: String { return self._s[1389]! } - public var IntentsSettings_Title: String { return self._s[1390]! } - public var GroupInfo_SetGroupPhotoDelete: String { return self._s[1392]! } - public var Privacy_ProfilePhoto_NeverShareWith_Title: String { return self._s[1393]! } - public var Channel_AdminLog_SendPolls: String { return self._s[1394]! } - public var Channel_AdminLog_TitleAllEvents: String { return self._s[1395]! } - public var Channel_EditAdmin_PermissionInviteMembers: String { return self._s[1396]! } - public var Contacts_MemberSearchSectionTitleGroup: String { return self._s[1397]! } - public var ScheduledMessages_DeleteMany: String { return self._s[1398]! } - public var Conversation_RestrictedStickers: String { return self._s[1399]! } - public var Notifications_ExceptionsResetToDefaults: String { return self._s[1401]! } - public var UserInfo_TelegramCall: String { return self._s[1403]! } - public var TwoStepAuth_SetupResendEmailCode: String { return self._s[1404]! } - public var CreatePoll_OptionsHeader: String { return self._s[1405]! } - public var SettingsSearch_Synonyms_Data_CallsUseLessData: String { return self._s[1406]! } - public var ArchivedChats_IntroTitle1: String { return self._s[1407]! } - public var Privacy_GroupsAndChannels_AlwaysAllow_Title: String { return self._s[1408]! } - public var Theme_Colors_Proceed: String { return self._s[1409]! } - public var Passport_Identity_EditPersonalDetails: String { return self._s[1410]! } + public var Stats_LanguagesTitle: String { return self._s[1374]! } + public var NotificationsSound_Glass: String { return self._s[1375]! } + public var Appearance_ThemeNightBlue: String { return self._s[1376]! } + public var CheckoutInfo_Pay: String { return self._s[1377]! } + public var PeerInfo_ButtonLeave: String { return self._s[1379]! } + public var Invite_LargeRecipientsCountWarning: String { return self._s[1380]! } + public var Call_CallAgain: String { return self._s[1382]! } + public var AttachmentMenu_SendAsFile: String { return self._s[1383]! } + public var AccessDenied_MicrophoneRestricted: String { return self._s[1384]! } + public var Passport_InvalidPasswordError: String { return self._s[1385]! } + public var Watch_Message_Game: String { return self._s[1386]! } + public var Stickers_Install: String { return self._s[1387]! } + public var VoiceOver_Chat_Message: String { return self._s[1388]! } + public var PrivacyLastSeenSettings_NeverShareWith: String { return self._s[1389]! } + public var Passport_Identity_ResidenceCountry: String { return self._s[1391]! } + public var Notifications_GroupNotificationsHelp: String { return self._s[1392]! } + public var AuthSessions_OtherSessions: String { return self._s[1393]! } + public var Channel_Username_Help: String { return self._s[1394]! } + public var Camera_Title: String { return self._s[1395]! } + public var IntentsSettings_Title: String { return self._s[1396]! } + public var GroupInfo_SetGroupPhotoDelete: String { return self._s[1398]! } + public var Privacy_ProfilePhoto_NeverShareWith_Title: String { return self._s[1399]! } + public var Channel_AdminLog_SendPolls: String { return self._s[1400]! } + public var Channel_AdminLog_TitleAllEvents: String { return self._s[1401]! } + public var Channel_EditAdmin_PermissionInviteMembers: String { return self._s[1402]! } + public var Contacts_MemberSearchSectionTitleGroup: String { return self._s[1403]! } + public var ScheduledMessages_DeleteMany: String { return self._s[1404]! } + public var Conversation_RestrictedStickers: String { return self._s[1405]! } + public var Notifications_ExceptionsResetToDefaults: String { return self._s[1407]! } + public var UserInfo_TelegramCall: String { return self._s[1409]! } + public var TwoStepAuth_SetupResendEmailCode: String { return self._s[1410]! } + public var CreatePoll_OptionsHeader: String { return self._s[1411]! } + public var SettingsSearch_Synonyms_Data_CallsUseLessData: String { return self._s[1412]! } + public var ArchivedChats_IntroTitle1: String { return self._s[1413]! } + public var Privacy_GroupsAndChannels_AlwaysAllow_Title: String { return self._s[1414]! } + public var Theme_Colors_Proceed: String { return self._s[1415]! } + public var Passport_Identity_EditPersonalDetails: String { return self._s[1416]! } public func Time_PreciseDate_m1(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1411]!, self._r[1411]!, [_1, _2, _3]) + return formatWithArgumentRanges(self._s[1417]!, self._r[1417]!, [_1, _2, _3]) } - public var Wallet_Month_GenAugust: String { return self._s[1412]! } - public var Settings_SaveEditedPhotos: String { return self._s[1413]! } - public var TwoStepAuth_ConfirmationTitle: String { return self._s[1414]! } - public var Privacy_GroupsAndChannels_NeverAllow_Title: String { return self._s[1415]! } - public var Conversation_MessageDialogRetry: String { return self._s[1416]! } - public var ChatList_Context_MarkAsUnread: String { return self._s[1417]! } - public var MessagePoll_SubmitVote: String { return self._s[1418]! } - public var Conversation_DiscardVoiceMessageAction: String { return self._s[1419]! } - public var Permissions_PeopleNearbyTitle_v0: String { return self._s[1420]! } - public var Group_Setup_TypeHeader: String { return self._s[1421]! } - public var Paint_RecentStickers: String { return self._s[1422]! } - public var PhotoEditor_GrainTool: String { return self._s[1423]! } - public var CheckoutInfo_ShippingInfoState: String { return self._s[1424]! } - public var EmptyGroupInfo_Line4: String { return self._s[1425]! } - public var Watch_AuthRequired: String { return self._s[1427]! } + public var Wallet_Month_GenAugust: String { return self._s[1418]! } + public var Settings_SaveEditedPhotos: String { return self._s[1419]! } + public var Stats_FollowersBySourceTitle: String { return self._s[1420]! } + public var TwoStepAuth_ConfirmationTitle: String { return self._s[1421]! } + public var Privacy_GroupsAndChannels_NeverAllow_Title: String { return self._s[1422]! } + public var Conversation_MessageDialogRetry: String { return self._s[1423]! } + public var ChatList_Context_MarkAsUnread: String { return self._s[1424]! } + public var MessagePoll_SubmitVote: String { return self._s[1425]! } + public var Conversation_DiscardVoiceMessageAction: String { return self._s[1426]! } + public var Permissions_PeopleNearbyTitle_v0: String { return self._s[1427]! } + public var Group_Setup_TypeHeader: String { return self._s[1428]! } + public var Paint_RecentStickers: String { return self._s[1429]! } + public var PhotoEditor_GrainTool: String { return self._s[1430]! } + public var CheckoutInfo_ShippingInfoState: String { return self._s[1431]! } + public var EmptyGroupInfo_Line4: String { return self._s[1432]! } + public var Watch_AuthRequired: String { return self._s[1434]! } public func Passport_Email_UseTelegramEmail(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1428]!, self._r[1428]!, [_0]) + return formatWithArgumentRanges(self._s[1435]!, self._r[1435]!, [_0]) } - public var Conversation_EncryptedDescriptionTitle: String { return self._s[1429]! } - public var ChannelIntro_Text: String { return self._s[1430]! } - public var DialogList_DeleteBotConfirmation: String { return self._s[1431]! } - public var GroupPermission_NoSendMedia: String { return self._s[1432]! } - public var Calls_AddTab: String { return self._s[1433]! } - public var Message_ReplyActionButtonShowReceipt: String { return self._s[1434]! } - public var Channel_AdminLog_EmptyFilterText: String { return self._s[1435]! } - public var Conversation_WalletRequiredSetup: String { return self._s[1436]! } - public var Notification_MessageLifetime1d: String { return self._s[1437]! } - public var Notifications_ChannelNotificationsExceptionsHelp: String { return self._s[1438]! } - public var Channel_BanUser_PermissionsHeader: String { return self._s[1439]! } - public var Passport_Identity_GenderFemale: String { return self._s[1440]! } - public var BlockedUsers_BlockTitle: String { return self._s[1441]! } + public var Conversation_EncryptedDescriptionTitle: String { return self._s[1436]! } + public var ChannelIntro_Text: String { return self._s[1437]! } + public var DialogList_DeleteBotConfirmation: String { return self._s[1438]! } + public var GroupPermission_NoSendMedia: String { return self._s[1439]! } + public var Calls_AddTab: String { return self._s[1440]! } + public var Message_ReplyActionButtonShowReceipt: String { return self._s[1441]! } + public var Channel_AdminLog_EmptyFilterText: String { return self._s[1442]! } + public var Conversation_WalletRequiredSetup: String { return self._s[1443]! } + public var Notification_MessageLifetime1d: String { return self._s[1444]! } + public var Notifications_ChannelNotificationsExceptionsHelp: String { return self._s[1445]! } + public var Channel_BanUser_PermissionsHeader: String { return self._s[1446]! } + public var Passport_Identity_GenderFemale: String { return self._s[1447]! } + public var BlockedUsers_BlockTitle: String { return self._s[1448]! } public func PUSH_CHANNEL_MESSAGE_GIF(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1442]!, self._r[1442]!, [_1]) + return formatWithArgumentRanges(self._s[1449]!, self._r[1449]!, [_1]) } - public var Weekday_Yesterday: String { return self._s[1443]! } - public var WallpaperSearch_ColorBlack: String { return self._s[1444]! } - public var Settings_Context_Logout: String { return self._s[1445]! } - public var Wallet_Info_UnknownTransaction: String { return self._s[1446]! } - public var ChatList_ArchiveAction: String { return self._s[1447]! } - public var AutoNightTheme_Scheduled: String { return self._s[1448]! } - public var TwoFactorSetup_Email_SkipAction: String { return self._s[1449]! } - public var Settings_Devices: String { return self._s[1450]! } - public var ContactInfo_Note: String { return self._s[1451]! } + public var Weekday_Yesterday: String { return self._s[1450]! } + public var WallpaperSearch_ColorBlack: String { return self._s[1451]! } + public var Settings_Context_Logout: String { return self._s[1452]! } + public var Wallet_Info_UnknownTransaction: String { return self._s[1453]! } + public var ChatList_ArchiveAction: String { return self._s[1454]! } + public var AutoNightTheme_Scheduled: String { return self._s[1455]! } + public var TwoFactorSetup_Email_SkipAction: String { return self._s[1456]! } + public var Settings_Devices: String { return self._s[1457]! } + public var ContactInfo_Note: String { return self._s[1458]! } public func Login_PhoneGenericEmailBody(_ _1: String, _ _2: String, _ _3: String, _ _4: String, _ _5: String, _ _6: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1452]!, self._r[1452]!, [_1, _2, _3, _4, _5, _6]) + return formatWithArgumentRanges(self._s[1459]!, self._r[1459]!, [_1, _2, _3, _4, _5, _6]) } - public var EditTheme_ThemeTemplateAlertTitle: String { return self._s[1453]! } - public var Wallet_Receive_CreateInvoice: String { return self._s[1454]! } - public var PrivacyPolicy_DeclineDeleteNow: String { return self._s[1455]! } - public var Theme_Colors_ColorWallpaperWarningProceed: String { return self._s[1456]! } + public var EditTheme_ThemeTemplateAlertTitle: String { return self._s[1460]! } + public var Wallet_Receive_CreateInvoice: String { return self._s[1461]! } + public var PrivacyPolicy_DeclineDeleteNow: String { return self._s[1462]! } + public var Theme_Colors_ColorWallpaperWarningProceed: String { return self._s[1463]! } public func PUSH_CHAT_JOINED(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1457]!, self._r[1457]!, [_1, _2]) + return formatWithArgumentRanges(self._s[1464]!, self._r[1464]!, [_1, _2]) } - public var CreatePoll_Create: String { return self._s[1458]! } - public var Channel_Members_AddBannedErrorAdmin: String { return self._s[1459]! } + public var CreatePoll_Create: String { return self._s[1465]! } + public var Channel_Members_AddBannedErrorAdmin: String { return self._s[1466]! } public func Notification_CallFormat(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1460]!, self._r[1460]!, [_1, _2]) + return formatWithArgumentRanges(self._s[1467]!, self._r[1467]!, [_1, _2]) } - public var ScheduledMessages_ClearAllConfirmation: String { return self._s[1461]! } - public var Checkout_ErrorProviderAccountInvalid: String { return self._s[1462]! } - public var Notifications_InAppNotificationsSounds: String { return self._s[1464]! } + public var ScheduledMessages_ClearAllConfirmation: String { return self._s[1468]! } + public var Checkout_ErrorProviderAccountInvalid: String { return self._s[1469]! } + public var Notifications_InAppNotificationsSounds: String { return self._s[1471]! } public func PUSH_PINNED_GAME_SCORE(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1465]!, self._r[1465]!, [_1]) + return formatWithArgumentRanges(self._s[1472]!, self._r[1472]!, [_1]) } - public var Preview_OpenInInstagram: String { return self._s[1466]! } - public var Notification_MessageLifetimeRemovedOutgoing: String { return self._s[1467]! } + public var Preview_OpenInInstagram: String { return self._s[1473]! } + public var Notification_MessageLifetimeRemovedOutgoing: String { return self._s[1474]! } public func PUSH_CHAT_ADD_MEMBER(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1468]!, self._r[1468]!, [_1, _2, _3]) + return formatWithArgumentRanges(self._s[1475]!, self._r[1475]!, [_1, _2, _3]) } public func Passport_PrivacyPolicy(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1469]!, self._r[1469]!, [_1, _2]) + return formatWithArgumentRanges(self._s[1476]!, self._r[1476]!, [_1, _2]) } - public var Channel_AdminLog_InfoPanelAlertTitle: String { return self._s[1470]! } - public var ArchivedChats_IntroText3: String { return self._s[1471]! } - public var ChatList_UndoArchiveHiddenText: String { return self._s[1472]! } - public var NetworkUsageSettings_TotalSection: String { return self._s[1473]! } - public var Wallet_Month_GenSeptember: String { return self._s[1474]! } - public var Channel_Setup_TypePrivateHelp: String { return self._s[1475]! } + public var Channel_AdminLog_InfoPanelAlertTitle: String { return self._s[1477]! } + public var ArchivedChats_IntroText3: String { return self._s[1478]! } + public var ChatList_UndoArchiveHiddenText: String { return self._s[1479]! } + public var NetworkUsageSettings_TotalSection: String { return self._s[1480]! } + public var Wallet_Month_GenSeptember: String { return self._s[1481]! } + public var Channel_Setup_TypePrivateHelp: String { return self._s[1482]! } public func PUSH_CHAT_MESSAGE_POLL(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1476]!, self._r[1476]!, [_1, _2, _3]) + return formatWithArgumentRanges(self._s[1483]!, self._r[1483]!, [_1, _2, _3]) } - public var Privacy_GroupsAndChannels_NeverAllow_Placeholder: String { return self._s[1478]! } - public var FastTwoStepSetup_HintSection: String { return self._s[1479]! } - public var Wallpaper_PhotoLibrary: String { return self._s[1480]! } - public var TwoStepAuth_SetupResendEmailCodeAlert: String { return self._s[1481]! } - public var Gif_NoGifsFound: String { return self._s[1482]! } - public var Watch_LastSeen_WithinAMonth: String { return self._s[1483]! } - public var VoiceOver_MessageContextDelete: String { return self._s[1484]! } - public var EditTheme_Preview: String { return self._s[1485]! } + public var Privacy_GroupsAndChannels_NeverAllow_Placeholder: String { return self._s[1485]! } + public var FastTwoStepSetup_HintSection: String { return self._s[1486]! } + public var Wallpaper_PhotoLibrary: String { return self._s[1487]! } + public var TwoStepAuth_SetupResendEmailCodeAlert: String { return self._s[1488]! } + public var Gif_NoGifsFound: String { return self._s[1489]! } + public var Watch_LastSeen_WithinAMonth: String { return self._s[1490]! } + public var VoiceOver_MessageContextDelete: String { return self._s[1491]! } + public var EditTheme_Preview: String { return self._s[1492]! } public func ClearCache_StorageTitle(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1486]!, self._r[1486]!, [_0]) + return formatWithArgumentRanges(self._s[1493]!, self._r[1493]!, [_0]) } - public var GroupInfo_ActionPromote: String { return self._s[1487]! } - public var PasscodeSettings_SimplePasscode: String { return self._s[1488]! } - public var GroupInfo_Permissions_Title: String { return self._s[1489]! } - public var Permissions_ContactsText_v0: String { return self._s[1490]! } - public var PrivacyPhoneNumberSettings_CustomDisabledHelp: String { return self._s[1491]! } - public var SettingsSearch_Synonyms_Notifications_BadgeIncludeMutedPublicGroups: String { return self._s[1492]! } - public var PrivacySettings_DataSettingsHelp: String { return self._s[1495]! } - public var Passport_FieldEmailHelp: String { return self._s[1496]! } + public var GroupInfo_ActionPromote: String { return self._s[1494]! } + public var PasscodeSettings_SimplePasscode: String { return self._s[1495]! } + public var GroupInfo_Permissions_Title: String { return self._s[1496]! } + public var Permissions_ContactsText_v0: String { return self._s[1497]! } + public var PrivacyPhoneNumberSettings_CustomDisabledHelp: String { return self._s[1498]! } + public var SettingsSearch_Synonyms_Notifications_BadgeIncludeMutedPublicGroups: String { return self._s[1499]! } + public var PrivacySettings_DataSettingsHelp: String { return self._s[1502]! } + public var Passport_FieldEmailHelp: String { return self._s[1503]! } public func Activity_RemindAboutUser(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1497]!, self._r[1497]!, [_0]) + return formatWithArgumentRanges(self._s[1504]!, self._r[1504]!, [_0]) } - public var Passport_Identity_GenderPlaceholder: String { return self._s[1498]! } - public var Weekday_ShortSaturday: String { return self._s[1499]! } - public var ContactInfo_PhoneLabelMain: String { return self._s[1500]! } - public var Watch_Conversation_UserInfo: String { return self._s[1501]! } - public var CheckoutInfo_ShippingInfoCityPlaceholder: String { return self._s[1502]! } - public var GroupPermission_PermissionDisabledByDefault: String { return self._s[1503]! } - public var PrivacyLastSeenSettings_Title: String { return self._s[1504]! } - public var Conversation_ShareBotLocationConfirmation: String { return self._s[1505]! } - public var PhotoEditor_VignetteTool: String { return self._s[1506]! } - public var Passport_Address_Street1Placeholder: String { return self._s[1507]! } - public var Passport_Language_et: String { return self._s[1508]! } - public var AppUpgrade_Running: String { return self._s[1509]! } - public var Channel_DiscussionGroup_Info: String { return self._s[1511]! } - public var EditTheme_Create_Preview_IncomingReplyName: String { return self._s[1512]! } - public var Passport_Language_bg: String { return self._s[1513]! } - public var Stickers_NoStickersFound: String { return self._s[1515]! } + public var Passport_Identity_GenderPlaceholder: String { return self._s[1505]! } + public var Weekday_ShortSaturday: String { return self._s[1506]! } + public var ContactInfo_PhoneLabelMain: String { return self._s[1507]! } + public var Watch_Conversation_UserInfo: String { return self._s[1508]! } + public var CheckoutInfo_ShippingInfoCityPlaceholder: String { return self._s[1509]! } + public var GroupPermission_PermissionDisabledByDefault: String { return self._s[1510]! } + public var PrivacyLastSeenSettings_Title: String { return self._s[1511]! } + public var Conversation_ShareBotLocationConfirmation: String { return self._s[1512]! } + public var PhotoEditor_VignetteTool: String { return self._s[1513]! } + public var Passport_Address_Street1Placeholder: String { return self._s[1514]! } + public var Passport_Language_et: String { return self._s[1515]! } + public var AppUpgrade_Running: String { return self._s[1516]! } + public var Channel_DiscussionGroup_Info: String { return self._s[1518]! } + public var EditTheme_Create_Preview_IncomingReplyName: String { return self._s[1519]! } + public var Passport_Language_bg: String { return self._s[1520]! } + public var Stickers_NoStickersFound: String { return self._s[1522]! } public func PUSH_CHANNEL_MESSAGE_TEXT(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1517]!, self._r[1517]!, [_1, _2]) + return formatWithArgumentRanges(self._s[1524]!, self._r[1524]!, [_1, _2]) } public func VoiceOver_Chat_ContactFrom(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1518]!, self._r[1518]!, [_0]) + return formatWithArgumentRanges(self._s[1525]!, self._r[1525]!, [_0]) } - public var Wallet_Month_GenJuly: String { return self._s[1519]! } - public var Wallet_Receive_AddressHeader: String { return self._s[1520]! } - public var Wallet_Send_AmountText: String { return self._s[1521]! } - public var Settings_About: String { return self._s[1522]! } + public var Wallet_Month_GenJuly: String { return self._s[1526]! } + public var Wallet_Receive_AddressHeader: String { return self._s[1527]! } + public var Wallet_Send_AmountText: String { return self._s[1528]! } + public var Settings_About: String { return self._s[1529]! } public func Channel_AdminLog_MessageRestricted(_ _0: String, _ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1523]!, self._r[1523]!, [_0, _1, _2]) + return formatWithArgumentRanges(self._s[1530]!, self._r[1530]!, [_0, _1, _2]) } - public var ChatList_Context_MarkAsRead: String { return self._s[1525]! } - public var KeyCommand_NewMessage: String { return self._s[1526]! } - public var Group_ErrorAddBlocked: String { return self._s[1527]! } + public var ChatList_Context_MarkAsRead: String { return self._s[1532]! } + public var KeyCommand_NewMessage: String { return self._s[1533]! } + public var Group_ErrorAddBlocked: String { return self._s[1534]! } public func Message_PaymentSent(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1528]!, self._r[1528]!, [_0]) + return formatWithArgumentRanges(self._s[1535]!, self._r[1535]!, [_0]) } - public var Map_LocationTitle: String { return self._s[1529]! } - public var ReportGroupLocation_Title: String { return self._s[1530]! } - public var CallSettings_UseLessDataLongDescription: String { return self._s[1531]! } - public var Cache_ClearProgress: String { return self._s[1532]! } + public var Map_LocationTitle: String { return self._s[1536]! } + public var ReportGroupLocation_Title: String { return self._s[1537]! } + public var CallSettings_UseLessDataLongDescription: String { return self._s[1538]! } + public var Cache_ClearProgress: String { return self._s[1539]! } public func Channel_Management_ErrorNotMember(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1533]!, self._r[1533]!, [_0]) + return formatWithArgumentRanges(self._s[1540]!, self._r[1540]!, [_0]) } - public var GroupRemoved_AddToGroup: String { return self._s[1534]! } - public var Passport_UpdateRequiredError: String { return self._s[1535]! } - public var Wallet_SecureStorageNotAvailable_Text: String { return self._s[1536]! } + public var GroupRemoved_AddToGroup: String { return self._s[1541]! } + public func External_OpenIn(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1542]!, self._r[1542]!, [_0]) + } + public var Passport_UpdateRequiredError: String { return self._s[1543]! } + public var Wallet_SecureStorageNotAvailable_Text: String { return self._s[1544]! } public func PUSH_MESSAGE_DOC(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1537]!, self._r[1537]!, [_1]) + return formatWithArgumentRanges(self._s[1545]!, self._r[1545]!, [_1]) } - public var Notifications_PermissionsSuppressWarningText: String { return self._s[1539]! } - public var Passport_Identity_MainPageHelp: String { return self._s[1540]! } - public var PeerInfo_ButtonSearch: String { return self._s[1541]! } - public var Conversation_StatusKickedFromGroup: String { return self._s[1542]! } - public var Passport_Language_ka: String { return self._s[1543]! } + public var Notifications_PermissionsSuppressWarningText: String { return self._s[1547]! } + public var Passport_Identity_MainPageHelp: String { return self._s[1548]! } + public var PeerInfo_ButtonSearch: String { return self._s[1549]! } + public var Conversation_StatusKickedFromGroup: String { return self._s[1550]! } + public var Passport_Language_ka: String { return self._s[1551]! } public func Wallet_Time_PreciseDate_m12(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1544]!, self._r[1544]!, [_1, _2, _3]) + return formatWithArgumentRanges(self._s[1552]!, self._r[1552]!, [_1, _2, _3]) } - public var Call_Decline: String { return self._s[1545]! } - public var SocksProxySetup_ProxyEnabled: String { return self._s[1546]! } - public var TwoFactorSetup_Email_SkipConfirmationText: String { return self._s[1549]! } + public var Call_Decline: String { return self._s[1553]! } + public var SocksProxySetup_ProxyEnabled: String { return self._s[1554]! } + public var TwoFactorSetup_Email_SkipConfirmationText: String { return self._s[1557]! } public func AuthCode_Alert(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1550]!, self._r[1550]!, [_0]) + return formatWithArgumentRanges(self._s[1558]!, self._r[1558]!, [_0]) } - public var CallFeedback_Send: String { return self._s[1551]! } - public var EditTheme_EditTitle: String { return self._s[1552]! } + public var CallFeedback_Send: String { return self._s[1559]! } + public var EditTheme_EditTitle: String { return self._s[1560]! } public func Channel_AdminLog_MessagePromotedNameUsername(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1553]!, self._r[1553]!, [_1, _2]) + return formatWithArgumentRanges(self._s[1561]!, self._r[1561]!, [_1, _2]) } - public var Passport_Phone_UseTelegramNumberHelp: String { return self._s[1554]! } + public var Passport_Phone_UseTelegramNumberHelp: String { return self._s[1562]! } public func Wallet_Updated_TodayAt(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1556]!, self._r[1556]!, [_0]) - } - public var SettingsSearch_Synonyms_Data_Title: String { return self._s[1557]! } - public var Passport_DeletePassport: String { return self._s[1558]! } - public var Appearance_AppIconFilled: String { return self._s[1559]! } - public var Privacy_Calls_P2PAlways: String { return self._s[1560]! } - public var Month_ShortDecember: String { return self._s[1561]! } - public var Channel_AdminLog_CanEditMessages: String { return self._s[1563]! } - public func Contacts_AccessDeniedHelpLandscape(_ _0: String) -> (String, [(Int, NSRange)]) { return formatWithArgumentRanges(self._s[1564]!, self._r[1564]!, [_0]) } - public var Channel_Stickers_Searching: String { return self._s[1565]! } - public var Conversation_EncryptedDescription1: String { return self._s[1566]! } - public var Conversation_EncryptedDescription2: String { return self._s[1567]! } - public var PasscodeSettings_PasscodeOptions: String { return self._s[1568]! } - public var Conversation_EncryptedDescription3: String { return self._s[1570]! } - public var PhotoEditor_SharpenTool: String { return self._s[1571]! } - public var Wallet_Configuration_Title: String { return self._s[1572]! } + public var SettingsSearch_Synonyms_Data_Title: String { return self._s[1565]! } + public var Passport_DeletePassport: String { return self._s[1566]! } + public var Appearance_AppIconFilled: String { return self._s[1567]! } + public var Privacy_Calls_P2PAlways: String { return self._s[1568]! } + public var Month_ShortDecember: String { return self._s[1569]! } + public var Channel_AdminLog_CanEditMessages: String { return self._s[1571]! } + public func Contacts_AccessDeniedHelpLandscape(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1572]!, self._r[1572]!, [_0]) + } + public var Channel_Stickers_Searching: String { return self._s[1573]! } + public var Conversation_EncryptedDescription1: String { return self._s[1574]! } + public var Conversation_EncryptedDescription2: String { return self._s[1575]! } + public var PasscodeSettings_PasscodeOptions: String { return self._s[1576]! } + public var Conversation_EncryptedDescription3: String { return self._s[1578]! } + public var PhotoEditor_SharpenTool: String { return self._s[1579]! } + public var Wallet_Configuration_Title: String { return self._s[1580]! } public func Conversation_AddNameToContacts(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1573]!, self._r[1573]!, [_0]) + return formatWithArgumentRanges(self._s[1581]!, self._r[1581]!, [_0]) } - public var Conversation_EncryptedDescription4: String { return self._s[1575]! } - public var Channel_Members_AddMembers: String { return self._s[1576]! } - public var Wallpaper_Search: String { return self._s[1577]! } - public var Weekday_Friday: String { return self._s[1579]! } - public var Privacy_ContactsSync: String { return self._s[1580]! } - public var SettingsSearch_Synonyms_Privacy_Data_ContactsReset: String { return self._s[1581]! } - public var ApplyLanguage_ChangeLanguageAction: String { return self._s[1582]! } + public var Conversation_EncryptedDescription4: String { return self._s[1583]! } + public var Channel_Members_AddMembers: String { return self._s[1584]! } + public var Wallpaper_Search: String { return self._s[1585]! } + public var Weekday_Friday: String { return self._s[1587]! } + public var Privacy_ContactsSync: String { return self._s[1588]! } + public var SettingsSearch_Synonyms_Privacy_Data_ContactsReset: String { return self._s[1589]! } + public var ApplyLanguage_ChangeLanguageAction: String { return self._s[1590]! } public func Channel_Management_RestrictedBy(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1583]!, self._r[1583]!, [_0]) + return formatWithArgumentRanges(self._s[1591]!, self._r[1591]!, [_0]) } - public var Wallet_Configuration_BlockchainIdHeader: String { return self._s[1584]! } - public var GroupInfo_Permissions_Removed: String { return self._s[1585]! } - public var ScheduledMessages_ScheduledOnline: String { return self._s[1586]! } - public var Passport_Identity_GenderMale: String { return self._s[1587]! } + public var Wallet_Configuration_BlockchainIdHeader: String { return self._s[1592]! } + public var GroupInfo_Permissions_Removed: String { return self._s[1593]! } + public var ScheduledMessages_ScheduledOnline: String { return self._s[1594]! } + public var Passport_Identity_GenderMale: String { return self._s[1595]! } public func Call_StatusBar(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1588]!, self._r[1588]!, [_0]) + return formatWithArgumentRanges(self._s[1596]!, self._r[1596]!, [_0]) } - public var Notifications_PermissionsKeepDisabled: String { return self._s[1589]! } - public var Conversation_JumpToDate: String { return self._s[1590]! } - public var Contacts_GlobalSearch: String { return self._s[1591]! } - public var AutoDownloadSettings_ResetHelp: String { return self._s[1592]! } - public var SettingsSearch_Synonyms_FAQ: String { return self._s[1593]! } - public var Profile_MessageLifetime1d: String { return self._s[1594]! } + public var Notifications_PermissionsKeepDisabled: String { return self._s[1597]! } + public var Conversation_JumpToDate: String { return self._s[1598]! } + public var Contacts_GlobalSearch: String { return self._s[1599]! } + public var AutoDownloadSettings_ResetHelp: String { return self._s[1600]! } + public var SettingsSearch_Synonyms_FAQ: String { return self._s[1601]! } + public var Profile_MessageLifetime1d: String { return self._s[1602]! } public func MESSAGE_INVOICE(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1595]!, self._r[1595]!, [_1, _2]) + return formatWithArgumentRanges(self._s[1603]!, self._r[1603]!, [_1, _2]) } - public var StickerPack_BuiltinPackName: String { return self._s[1598]! } + public var StickerPack_BuiltinPackName: String { return self._s[1606]! } public func PUSH_CHAT_MESSAGE_AUDIO(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1599]!, self._r[1599]!, [_1, _2]) + return formatWithArgumentRanges(self._s[1607]!, self._r[1607]!, [_1, _2]) } - public var VoiceOver_Chat_RecordModeVoiceMessageInfo: String { return self._s[1600]! } - public var Passport_InfoTitle: String { return self._s[1602]! } - public var Notifications_PermissionsUnreachableText: String { return self._s[1603]! } + public var VoiceOver_Chat_RecordModeVoiceMessageInfo: String { return self._s[1608]! } + public var Passport_InfoTitle: String { return self._s[1610]! } + public var Notifications_PermissionsUnreachableText: String { return self._s[1611]! } public func NetworkUsageSettings_CellularUsageSince(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1607]!, self._r[1607]!, [_0]) + return formatWithArgumentRanges(self._s[1615]!, self._r[1615]!, [_0]) } public func PUSH_CHAT_MESSAGE_GEO(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1608]!, self._r[1608]!, [_1, _2]) + return formatWithArgumentRanges(self._s[1616]!, self._r[1616]!, [_1, _2]) } - public var Passport_Address_TypePassportRegistrationUploadScan: String { return self._s[1609]! } - public var Profile_BotInfo: String { return self._s[1610]! } - public var Watch_Compose_CreateMessage: String { return self._s[1611]! } - public var AutoDownloadSettings_VoiceMessagesInfo: String { return self._s[1612]! } - public var Month_ShortNovember: String { return self._s[1613]! } - public var Conversation_ScamWarning: String { return self._s[1614]! } - public var Wallpaper_SetCustomBackground: String { return self._s[1615]! } - public var Appearance_TextSize_Title: String { return self._s[1616]! } - public var Passport_Identity_TranslationsHelp: String { return self._s[1617]! } - public var NotificationsSound_Chime: String { return self._s[1618]! } - public var Passport_Language_ko: String { return self._s[1620]! } - public var InviteText_URL: String { return self._s[1621]! } - public var TextFormat_Monospace: String { return self._s[1622]! } + public var Passport_Address_TypePassportRegistrationUploadScan: String { return self._s[1617]! } + public var Profile_BotInfo: String { return self._s[1618]! } + public var Watch_Compose_CreateMessage: String { return self._s[1619]! } + public var AutoDownloadSettings_VoiceMessagesInfo: String { return self._s[1620]! } + public var Month_ShortNovember: String { return self._s[1621]! } + public var Conversation_ScamWarning: String { return self._s[1622]! } + public var Wallpaper_SetCustomBackground: String { return self._s[1623]! } + public var Appearance_TextSize_Title: String { return self._s[1624]! } + public var Passport_Identity_TranslationsHelp: String { return self._s[1625]! } + public var NotificationsSound_Chime: String { return self._s[1626]! } + public var Passport_Language_ko: String { return self._s[1628]! } + public var InviteText_URL: String { return self._s[1629]! } + public var TextFormat_Monospace: String { return self._s[1630]! } public func Time_PreciseDate_m11(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1623]!, self._r[1623]!, [_1, _2, _3]) + return formatWithArgumentRanges(self._s[1631]!, self._r[1631]!, [_1, _2, _3]) } - public var EditTheme_Edit_BottomInfo: String { return self._s[1624]! } + public var EditTheme_Edit_BottomInfo: String { return self._s[1632]! } public func Login_WillSendSms(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1625]!, self._r[1625]!, [_0]) + return formatWithArgumentRanges(self._s[1633]!, self._r[1633]!, [_0]) } public func Watch_Time_ShortWeekdayAt(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1626]!, self._r[1626]!, [_1, _2]) + return formatWithArgumentRanges(self._s[1634]!, self._r[1634]!, [_1, _2]) } - public var Wallet_Words_Title: String { return self._s[1627]! } - public var Wallet_Month_ShortMay: String { return self._s[1628]! } - public var EditTheme_CreateTitle: String { return self._s[1630]! } - public var Passport_InfoLearnMore: String { return self._s[1631]! } - public var TwoStepAuth_EmailPlaceholder: String { return self._s[1632]! } - public var Passport_Identity_AddIdentityCard: String { return self._s[1633]! } - public var Your_card_has_expired: String { return self._s[1634]! } - public var StickerPacksSettings_StickerPacksSection: String { return self._s[1635]! } - public var GroupInfo_InviteLink_Help: String { return self._s[1636]! } - public var TwoFactorSetup_EmailVerification_ResendAction: String { return self._s[1640]! } - public var Conversation_Report: String { return self._s[1642]! } - public var Notifications_MessageNotificationsSound: String { return self._s[1643]! } - public var Notification_MessageLifetime1m: String { return self._s[1644]! } - public var Privacy_ContactsTitle: String { return self._s[1645]! } - public var Conversation_ShareMyContactInfo: String { return self._s[1646]! } - public var Wallet_WordCheck_Title: String { return self._s[1647]! } - public var ChannelMembers_WhoCanAddMembersAdminsHelp: String { return self._s[1648]! } - public var Channel_Members_Title: String { return self._s[1649]! } - public var Map_OpenInWaze: String { return self._s[1650]! } - public var Appearance_RemoveThemeColorConfirmation: String { return self._s[1651]! } - public var Login_PhoneBannedError: String { return self._s[1652]! } + public var Wallet_Words_Title: String { return self._s[1635]! } + public var Wallet_Month_ShortMay: String { return self._s[1636]! } + public var EditTheme_CreateTitle: String { return self._s[1638]! } + public var Passport_InfoLearnMore: String { return self._s[1639]! } + public var TwoStepAuth_EmailPlaceholder: String { return self._s[1640]! } + public var Passport_Identity_AddIdentityCard: String { return self._s[1641]! } + public var Your_card_has_expired: String { return self._s[1642]! } + public var StickerPacksSettings_StickerPacksSection: String { return self._s[1643]! } + public var GroupInfo_InviteLink_Help: String { return self._s[1644]! } + public var TwoFactorSetup_EmailVerification_ResendAction: String { return self._s[1648]! } + public var Conversation_Report: String { return self._s[1650]! } + public var Notifications_MessageNotificationsSound: String { return self._s[1651]! } + public var Notification_MessageLifetime1m: String { return self._s[1652]! } + public var Privacy_ContactsTitle: String { return self._s[1653]! } + public var Conversation_ShareMyContactInfo: String { return self._s[1654]! } + public var Wallet_WordCheck_Title: String { return self._s[1655]! } + public var ChannelMembers_WhoCanAddMembersAdminsHelp: String { return self._s[1656]! } + public var Channel_Members_Title: String { return self._s[1657]! } + public var Map_OpenInWaze: String { return self._s[1658]! } + public var Appearance_RemoveThemeColorConfirmation: String { return self._s[1659]! } + public var Login_PhoneBannedError: String { return self._s[1660]! } public func LiveLocationUpdated_YesterdayAt(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1653]!, self._r[1653]!, [_0]) + return formatWithArgumentRanges(self._s[1661]!, self._r[1661]!, [_0]) } - public var IntentsSettings_MainAccount: String { return self._s[1654]! } - public var Group_Management_AddModeratorHelp: String { return self._s[1655]! } - public var AutoDownloadSettings_WifiTitle: String { return self._s[1656]! } - public var Common_OK: String { return self._s[1657]! } - public var Passport_Address_TypeBankStatementUploadScan: String { return self._s[1658]! } - public var Wallet_Words_NotDoneResponse: String { return self._s[1659]! } - public var Cache_Music: String { return self._s[1660]! } - public var Wallet_Configuration_SourceURL: String { return self._s[1661]! } - public var SettingsSearch_Synonyms_EditProfile_PhoneNumber: String { return self._s[1662]! } - public var PasscodeSettings_UnlockWithTouchId: String { return self._s[1665]! } - public var TwoStepAuth_HintPlaceholder: String { return self._s[1666]! } + public var IntentsSettings_MainAccount: String { return self._s[1662]! } + public var Group_Management_AddModeratorHelp: String { return self._s[1663]! } + public var AutoDownloadSettings_WifiTitle: String { return self._s[1664]! } + public var Common_OK: String { return self._s[1665]! } + public var Passport_Address_TypeBankStatementUploadScan: String { return self._s[1666]! } + public var Wallet_Words_NotDoneResponse: String { return self._s[1667]! } + public var Cache_Music: String { return self._s[1668]! } + public var Wallet_Configuration_SourceURL: String { return self._s[1669]! } + public var SettingsSearch_Synonyms_EditProfile_PhoneNumber: String { return self._s[1670]! } + public var PasscodeSettings_UnlockWithTouchId: String { return self._s[1673]! } + public var ChatList_EmptyChatListEditFilter: String { return self._s[1674]! } + public var TwoStepAuth_HintPlaceholder: String { return self._s[1675]! } public func PUSH_PINNED_INVOICE(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1667]!, self._r[1667]!, [_1]) + return formatWithArgumentRanges(self._s[1676]!, self._r[1676]!, [_1]) } public func Passport_RequestHeader(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1668]!, self._r[1668]!, [_0]) + return formatWithArgumentRanges(self._s[1677]!, self._r[1677]!, [_0]) } - public var TwoFactorSetup_Done_Action: String { return self._s[1669]! } + public var TwoFactorSetup_Done_Action: String { return self._s[1678]! } public func VoiceOver_Chat_ContactOrganization(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1670]!, self._r[1670]!, [_0]) + return formatWithArgumentRanges(self._s[1679]!, self._r[1679]!, [_0]) } - public var Wallet_Send_ErrorNotEnoughFundsText: String { return self._s[1671]! } - public var Watch_MessageView_ViewOnPhone: String { return self._s[1673]! } - public var Privacy_Calls_CustomShareHelp: String { return self._s[1674]! } - public var Wallet_Receive_CreateInvoiceInfo: String { return self._s[1676]! } - public var ChangePhoneNumberNumber_Title: String { return self._s[1677]! } - public var State_ConnectingToProxyInfo: String { return self._s[1678]! } - public var Conversation_SwipeToReplyHintTitle: String { return self._s[1679]! } - public var Message_VideoMessage: String { return self._s[1681]! } - public var ChannelInfo_DeleteChannel: String { return self._s[1682]! } - public var ContactInfo_PhoneLabelOther: String { return self._s[1683]! } - public var Channel_EditAdmin_CannotEdit: String { return self._s[1684]! } - public var Passport_DeleteAddressConfirmation: String { return self._s[1685]! } + public var Wallet_Send_ErrorNotEnoughFundsText: String { return self._s[1680]! } + public var Watch_MessageView_ViewOnPhone: String { return self._s[1682]! } + public var Privacy_Calls_CustomShareHelp: String { return self._s[1683]! } + public var Wallet_Receive_CreateInvoiceInfo: String { return self._s[1685]! } + public var ChangePhoneNumberNumber_Title: String { return self._s[1686]! } + public var State_ConnectingToProxyInfo: String { return self._s[1687]! } + public var Conversation_SwipeToReplyHintTitle: String { return self._s[1688]! } + public var Message_VideoMessage: String { return self._s[1690]! } + public var ChannelInfo_DeleteChannel: String { return self._s[1691]! } + public var ContactInfo_PhoneLabelOther: String { return self._s[1692]! } + public var Channel_EditAdmin_CannotEdit: String { return self._s[1693]! } + public var Passport_DeleteAddressConfirmation: String { return self._s[1694]! } public func Wallet_Time_PreciseDate_m9(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1686]!, self._r[1686]!, [_1, _2, _3]) + return formatWithArgumentRanges(self._s[1695]!, self._r[1695]!, [_1, _2, _3]) } - public var WallpaperPreview_SwipeBottomText: String { return self._s[1687]! } - public var Activity_RecordingAudio: String { return self._s[1688]! } - public var SettingsSearch_Synonyms_Watch: String { return self._s[1689]! } - public var PasscodeSettings_TryAgainIn1Minute: String { return self._s[1690]! } - public var Wallet_Info_Address: String { return self._s[1691]! } + public var WallpaperPreview_SwipeBottomText: String { return self._s[1696]! } + public var Activity_RecordingAudio: String { return self._s[1697]! } + public var SettingsSearch_Synonyms_Watch: String { return self._s[1698]! } + public var PasscodeSettings_TryAgainIn1Minute: String { return self._s[1699]! } + public var Wallet_Info_Address: String { return self._s[1700]! } public func Notification_ChangedGroupName(_ _0: String, _ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1693]!, self._r[1693]!, [_0, _1]) + return formatWithArgumentRanges(self._s[1702]!, self._r[1702]!, [_0, _1]) } public func EmptyGroupInfo_Line1(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1697]!, self._r[1697]!, [_0]) + return formatWithArgumentRanges(self._s[1706]!, self._r[1706]!, [_0]) } - public var Conversation_ApplyLocalization: String { return self._s[1698]! } - public var TwoFactorSetup_Intro_Action: String { return self._s[1699]! } - public var UserInfo_AddPhone: String { return self._s[1700]! } - public var Map_ShareLiveLocationHelp: String { return self._s[1701]! } + public var Conversation_ApplyLocalization: String { return self._s[1707]! } + public var TwoFactorSetup_Intro_Action: String { return self._s[1708]! } + public var UserInfo_AddPhone: String { return self._s[1709]! } + public var Map_ShareLiveLocationHelp: String { return self._s[1710]! } public func Passport_Identity_NativeNameGenericHelp(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1702]!, self._r[1702]!, [_0]) + return formatWithArgumentRanges(self._s[1711]!, self._r[1711]!, [_0]) } - public var Passport_Scans: String { return self._s[1704]! } - public var BlockedUsers_Unblock: String { return self._s[1705]! } + public var Passport_Scans: String { return self._s[1713]! } + public var BlockedUsers_Unblock: String { return self._s[1714]! } public func PUSH_ENCRYPTION_REQUEST(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1706]!, self._r[1706]!, [_1]) + return formatWithArgumentRanges(self._s[1715]!, self._r[1715]!, [_1]) } - public var Channel_Management_LabelCreator: String { return self._s[1707]! } - public var Conversation_ReportSpamAndLeave: String { return self._s[1708]! } - public var SettingsSearch_Synonyms_EditProfile_Bio: String { return self._s[1709]! } - public var ChatList_UndoArchiveMultipleTitle: String { return self._s[1710]! } - public var Passport_Identity_NativeNameGenericTitle: String { return self._s[1711]! } + public var Channel_Management_LabelCreator: String { return self._s[1716]! } + public var Conversation_ReportSpamAndLeave: String { return self._s[1717]! } + public var SettingsSearch_Synonyms_EditProfile_Bio: String { return self._s[1718]! } + public var ChatList_UndoArchiveMultipleTitle: String { return self._s[1719]! } + public var Passport_Identity_NativeNameGenericTitle: String { return self._s[1720]! } public func Login_EmailPhoneBody(_ _0: String, _ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1712]!, self._r[1712]!, [_0, _1, _2]) + return formatWithArgumentRanges(self._s[1721]!, self._r[1721]!, [_0, _1, _2]) } - public var Login_PhoneNumberHelp: String { return self._s[1713]! } - public var LastSeen_ALongTimeAgo: String { return self._s[1714]! } - public var Channel_AdminLog_CanPinMessages: String { return self._s[1715]! } - public var ChannelIntro_CreateChannel: String { return self._s[1716]! } - public var Conversation_UnreadMessages: String { return self._s[1717]! } - public var SettingsSearch_Synonyms_Stickers_ArchivedPacks: String { return self._s[1718]! } - public var Channel_AdminLog_EmptyText: String { return self._s[1719]! } - public var Theme_Context_Apply: String { return self._s[1720]! } - public var Notification_GroupActivated: String { return self._s[1721]! } - public var NotificationSettings_ContactJoinedInfo: String { return self._s[1722]! } - public var Wallet_Intro_CreateWallet: String { return self._s[1723]! } + public var Login_PhoneNumberHelp: String { return self._s[1722]! } + public var LastSeen_ALongTimeAgo: String { return self._s[1723]! } + public var Channel_AdminLog_CanPinMessages: String { return self._s[1724]! } + public var ChannelIntro_CreateChannel: String { return self._s[1725]! } + public var Conversation_UnreadMessages: String { return self._s[1726]! } + public var SettingsSearch_Synonyms_Stickers_ArchivedPacks: String { return self._s[1727]! } + public var Channel_AdminLog_EmptyText: String { return self._s[1728]! } + public var Theme_Context_Apply: String { return self._s[1729]! } + public var Notification_GroupActivated: String { return self._s[1730]! } + public var NotificationSettings_ContactJoinedInfo: String { return self._s[1731]! } + public var Wallet_Intro_CreateWallet: String { return self._s[1732]! } public func Notification_PinnedContactMessage(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1724]!, self._r[1724]!, [_0]) - } - public func DownloadingStatus(_ _0: String, _ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1725]!, self._r[1725]!, [_0, _1]) - } - public var GroupInfo_ConvertToSupergroup: String { return self._s[1727]! } - public func PrivacyPolicy_AgeVerificationMessage(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1728]!, self._r[1728]!, [_0]) - } - public var Undo_DeletedChannel: String { return self._s[1729]! } - public var CallFeedback_AddComment: String { return self._s[1730]! } - public func Conversation_OpenBotLinkAllowMessages(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1731]!, self._r[1731]!, [_0]) - } - public var Document_TargetConfirmationFormat: String { return self._s[1732]! } - public func Call_StatusOngoing(_ _0: String) -> (String, [(Int, NSRange)]) { return formatWithArgumentRanges(self._s[1733]!, self._r[1733]!, [_0]) } - public var LogoutOptions_SetPasscodeTitle: String { return self._s[1734]! } + public func DownloadingStatus(_ _0: String, _ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1734]!, self._r[1734]!, [_0, _1]) + } + public var GroupInfo_ConvertToSupergroup: String { return self._s[1736]! } + public func PrivacyPolicy_AgeVerificationMessage(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1737]!, self._r[1737]!, [_0]) + } + public var Undo_DeletedChannel: String { return self._s[1738]! } + public var CallFeedback_AddComment: String { return self._s[1739]! } + public func Conversation_OpenBotLinkAllowMessages(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1740]!, self._r[1740]!, [_0]) + } + public var Document_TargetConfirmationFormat: String { return self._s[1741]! } + public func Call_StatusOngoing(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1742]!, self._r[1742]!, [_0]) + } + public var LogoutOptions_SetPasscodeTitle: String { return self._s[1743]! } public func PUSH_CHAT_MESSAGE_GAME_SCORE(_ _1: String, _ _2: String, _ _3: String, _ _4: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1735]!, self._r[1735]!, [_1, _2, _3, _4]) + return formatWithArgumentRanges(self._s[1744]!, self._r[1744]!, [_1, _2, _3, _4]) } - public var Wallet_SecureStorageChanged_PasscodeText: String { return self._s[1736]! } - public var Theme_ErrorNotFound: String { return self._s[1737]! } - public var Contacts_SortByName: String { return self._s[1738]! } - public var SettingsSearch_Synonyms_Privacy_Forwards: String { return self._s[1739]! } + public var Wallet_SecureStorageChanged_PasscodeText: String { return self._s[1745]! } + public var Theme_ErrorNotFound: String { return self._s[1746]! } + public var Contacts_SortByName: String { return self._s[1747]! } + public var SettingsSearch_Synonyms_Privacy_Forwards: String { return self._s[1748]! } public func CHAT_MESSAGE_INVOICE(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1741]!, self._r[1741]!, [_1, _2, _3]) + return formatWithArgumentRanges(self._s[1750]!, self._r[1750]!, [_1, _2, _3]) } - public var Notification_Exceptions_RemoveFromExceptions: String { return self._s[1742]! } - public var ScheduledMessages_EditTime: String { return self._s[1743]! } - public var Conversation_ClearSelfHistory: String { return self._s[1744]! } - public var Checkout_NewCard_PostcodePlaceholder: String { return self._s[1745]! } - public var PasscodeSettings_DoNotMatch: String { return self._s[1746]! } - public var Stickers_SuggestNone: String { return self._s[1747]! } - public var ChatSettings_Cache: String { return self._s[1748]! } - public var Settings_SaveIncomingPhotos: String { return self._s[1749]! } - public var Media_ShareThisPhoto: String { return self._s[1750]! } - public var Chat_SlowmodeTooltipPending: String { return self._s[1751]! } - public var InfoPlist_NSContactsUsageDescription: String { return self._s[1752]! } - public var Conversation_ContextMenuCopyLink: String { return self._s[1753]! } - public var PrivacyPolicy_AgeVerificationTitle: String { return self._s[1754]! } - public var SettingsSearch_Synonyms_Stickers_Masks: String { return self._s[1755]! } - public var TwoStepAuth_SetupPasswordEnterPasswordNew: String { return self._s[1756]! } - public var Appearance_ThemePreview_Chat_6_Text: String { return self._s[1757]! } + public var Notification_Exceptions_RemoveFromExceptions: String { return self._s[1751]! } + public var ScheduledMessages_EditTime: String { return self._s[1752]! } + public var Conversation_ClearSelfHistory: String { return self._s[1753]! } + public var Checkout_NewCard_PostcodePlaceholder: String { return self._s[1754]! } + public var PasscodeSettings_DoNotMatch: String { return self._s[1755]! } + public var Stickers_SuggestNone: String { return self._s[1756]! } + public var ChatSettings_Cache: String { return self._s[1757]! } + public var Settings_SaveIncomingPhotos: String { return self._s[1758]! } + public var Media_ShareThisPhoto: String { return self._s[1759]! } + public var Chat_SlowmodeTooltipPending: String { return self._s[1760]! } + public var InfoPlist_NSContactsUsageDescription: String { return self._s[1761]! } + public var Conversation_ContextMenuCopyLink: String { return self._s[1762]! } + public var PrivacyPolicy_AgeVerificationTitle: String { return self._s[1763]! } + public var SettingsSearch_Synonyms_Stickers_Masks: String { return self._s[1764]! } + public var TwoStepAuth_SetupPasswordEnterPasswordNew: String { return self._s[1765]! } + public var Appearance_ThemePreview_Chat_6_Text: String { return self._s[1766]! } public func Wallet_SecureStorageReset_BiometryText(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1758]!, self._r[1758]!, [_0]) + return formatWithArgumentRanges(self._s[1767]!, self._r[1767]!, [_0]) } - public var Permissions_CellularDataTitle_v0: String { return self._s[1759]! } - public var WallpaperSearch_ColorWhite: String { return self._s[1761]! } - public var Channel_AdminLog_DefaultRestrictionsUpdated: String { return self._s[1762]! } - public var Conversation_ErrorInaccessibleMessage: String { return self._s[1763]! } - public var Map_OpenIn: String { return self._s[1764]! } - public var PeerInfo_ButtonCall: String { return self._s[1765]! } + public var Permissions_CellularDataTitle_v0: String { return self._s[1768]! } + public var WallpaperSearch_ColorWhite: String { return self._s[1770]! } + public var Channel_AdminLog_DefaultRestrictionsUpdated: String { return self._s[1771]! } + public var Conversation_ErrorInaccessibleMessage: String { return self._s[1772]! } + public var Map_OpenIn: String { return self._s[1773]! } + public var PeerInfo_ButtonCall: String { return self._s[1774]! } public func PUSH_PHONE_CALL_MISSED(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1768]!, self._r[1768]!, [_1]) + return formatWithArgumentRanges(self._s[1777]!, self._r[1777]!, [_1]) } public func ChannelInfo_AddParticipantConfirmation(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1769]!, self._r[1769]!, [_0]) + return formatWithArgumentRanges(self._s[1778]!, self._r[1778]!, [_0]) } - public var GroupInfo_Permissions_SlowmodeHeader: String { return self._s[1770]! } - public var MessagePoll_LabelClosed: String { return self._s[1771]! } - public var GroupPermission_PermissionGloballyDisabled: String { return self._s[1773]! } - public var Wallet_Send_SendAnyway: String { return self._s[1774]! } - public var Passport_Identity_MiddleNamePlaceholder: String { return self._s[1775]! } - public var UserInfo_FirstNamePlaceholder: String { return self._s[1776]! } - public var PrivacyLastSeenSettings_WhoCanSeeMyTimestamp: String { return self._s[1777]! } - public var Map_SetThisPlace: String { return self._s[1778]! } - public var Login_SelectCountry_Title: String { return self._s[1779]! } - public var Channel_EditAdmin_PermissionBanUsers: String { return self._s[1780]! } + public var GroupInfo_Permissions_SlowmodeHeader: String { return self._s[1779]! } + public var MessagePoll_LabelClosed: String { return self._s[1780]! } + public var GroupPermission_PermissionGloballyDisabled: String { return self._s[1782]! } + public var Wallet_Send_SendAnyway: String { return self._s[1783]! } + public var Passport_Identity_MiddleNamePlaceholder: String { return self._s[1784]! } + public var UserInfo_FirstNamePlaceholder: String { return self._s[1785]! } + public var PrivacyLastSeenSettings_WhoCanSeeMyTimestamp: String { return self._s[1786]! } + public var Map_SetThisPlace: String { return self._s[1787]! } + public var Login_SelectCountry_Title: String { return self._s[1788]! } + public var Channel_EditAdmin_PermissionBanUsers: String { return self._s[1789]! } public func Conversation_OpenBotLinkLogin(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1781]!, self._r[1781]!, [_1, _2]) + return formatWithArgumentRanges(self._s[1790]!, self._r[1790]!, [_1, _2]) } - public var Channel_AdminLog_ChangeInfo: String { return self._s[1782]! } - public var Watch_Suggestion_BRB: String { return self._s[1783]! } - public var Passport_Identity_EditIdentityCard: String { return self._s[1784]! } - public var Contacts_PermissionsTitle: String { return self._s[1785]! } - public var Conversation_RestrictedInline: String { return self._s[1786]! } - public var Appearance_RemoveThemeColor: String { return self._s[1788]! } - public var StickerPack_ViewPack: String { return self._s[1789]! } - public var Wallet_UnknownError: String { return self._s[1790]! } + public var Channel_AdminLog_ChangeInfo: String { return self._s[1791]! } + public var Watch_Suggestion_BRB: String { return self._s[1792]! } + public var Passport_Identity_EditIdentityCard: String { return self._s[1793]! } + public var Contacts_PermissionsTitle: String { return self._s[1794]! } + public var Conversation_RestrictedInline: String { return self._s[1795]! } + public var Appearance_RemoveThemeColor: String { return self._s[1797]! } + public var StickerPack_ViewPack: String { return self._s[1798]! } + public var Wallet_UnknownError: String { return self._s[1799]! } public func Update_AppVersion(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1791]!, self._r[1791]!, [_0]) + return formatWithArgumentRanges(self._s[1800]!, self._r[1800]!, [_0]) } - public var Compose_NewChannel: String { return self._s[1793]! } - public var ChatSettings_AutoDownloadSettings_TypePhoto: String { return self._s[1796]! } - public var MessagePoll_LabelQuiz: String { return self._s[1798]! } - public var Conversation_ReportSpamGroupConfirmation: String { return self._s[1799]! } - public var Channel_Info_Stickers: String { return self._s[1800]! } - public var AutoNightTheme_PreferredTheme: String { return self._s[1801]! } - public var PrivacyPolicy_AgeVerificationAgree: String { return self._s[1802]! } - public var Passport_DeletePersonalDetails: String { return self._s[1803]! } - public var LogoutOptions_AddAccountTitle: String { return self._s[1804]! } - public var Channel_DiscussionGroupInfo: String { return self._s[1805]! } - public var Group_EditAdmin_RankOwnerPlaceholder: String { return self._s[1806]! } - public var Conversation_SearchNoResults: String { return self._s[1809]! } - public var Wallet_Configuration_ApplyErrorTextURLInvalid: String { return self._s[1810]! } - public var MessagePoll_LabelAnonymous: String { return self._s[1811]! } - public var Channel_Members_AddAdminErrorNotAMember: String { return self._s[1812]! } - public var Login_Code: String { return self._s[1813]! } - public var EditTheme_Create_BottomInfo: String { return self._s[1814]! } - public var Watch_Suggestion_WhatsUp: String { return self._s[1815]! } - public var Weekday_ShortThursday: String { return self._s[1816]! } - public var Resolve_ErrorNotFound: String { return self._s[1818]! } - public var LastSeen_Offline: String { return self._s[1819]! } - public var PeopleNearby_NoMembers: String { return self._s[1820]! } - public var GroupPermission_AddMembersNotAvailable: String { return self._s[1821]! } - public var Privacy_Calls_AlwaysAllow_Title: String { return self._s[1822]! } - public var GroupInfo_Title: String { return self._s[1824]! } - public var NotificationsSound_Note: String { return self._s[1825]! } - public var Conversation_EditingMessagePanelTitle: String { return self._s[1826]! } - public var Watch_Message_Poll: String { return self._s[1827]! } - public var Privacy_Calls: String { return self._s[1828]! } + public var Compose_NewChannel: String { return self._s[1802]! } + public var ChatSettings_AutoDownloadSettings_TypePhoto: String { return self._s[1805]! } + public var MessagePoll_LabelQuiz: String { return self._s[1807]! } + public var Conversation_ReportSpamGroupConfirmation: String { return self._s[1808]! } + public var Channel_Info_Stickers: String { return self._s[1809]! } + public var AutoNightTheme_PreferredTheme: String { return self._s[1810]! } + public var PrivacyPolicy_AgeVerificationAgree: String { return self._s[1811]! } + public var Passport_DeletePersonalDetails: String { return self._s[1812]! } + public var LogoutOptions_AddAccountTitle: String { return self._s[1813]! } + public var Channel_DiscussionGroupInfo: String { return self._s[1814]! } + public var Group_EditAdmin_RankOwnerPlaceholder: String { return self._s[1815]! } + public var Conversation_SearchNoResults: String { return self._s[1818]! } + public var Wallet_Configuration_ApplyErrorTextURLInvalid: String { return self._s[1819]! } + public var MessagePoll_LabelAnonymous: String { return self._s[1820]! } + public var Channel_Members_AddAdminErrorNotAMember: String { return self._s[1821]! } + public var Login_Code: String { return self._s[1822]! } + public var EditTheme_Create_BottomInfo: String { return self._s[1823]! } + public var Watch_Suggestion_WhatsUp: String { return self._s[1824]! } + public var Weekday_ShortThursday: String { return self._s[1825]! } + public var Resolve_ErrorNotFound: String { return self._s[1827]! } + public var LastSeen_Offline: String { return self._s[1828]! } + public var PeopleNearby_NoMembers: String { return self._s[1829]! } + public var GroupPermission_AddMembersNotAvailable: String { return self._s[1830]! } + public var Privacy_Calls_AlwaysAllow_Title: String { return self._s[1831]! } + public var GroupInfo_Title: String { return self._s[1833]! } + public var NotificationsSound_Note: String { return self._s[1834]! } + public var Conversation_EditingMessagePanelTitle: String { return self._s[1835]! } + public var Watch_Message_Poll: String { return self._s[1836]! } + public var Privacy_Calls: String { return self._s[1837]! } public func Channel_AdminLog_MessageRankUsername(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1829]!, self._r[1829]!, [_1, _2, _3]) + return formatWithArgumentRanges(self._s[1838]!, self._r[1838]!, [_1, _2, _3]) } - public var Month_ShortAugust: String { return self._s[1830]! } - public var TwoStepAuth_SetPasswordHelp: String { return self._s[1831]! } - public var Notifications_Reset: String { return self._s[1832]! } - public var Conversation_Pin: String { return self._s[1833]! } - public var Passport_Language_lv: String { return self._s[1834]! } - public var Permissions_PeopleNearbyAllowInSettings_v0: String { return self._s[1835]! } - public var BlockedUsers_Info: String { return self._s[1836]! } - public var SettingsSearch_Synonyms_Data_AutoplayVideos: String { return self._s[1838]! } - public var Watch_Conversation_Unblock: String { return self._s[1840]! } + public var Month_ShortAugust: String { return self._s[1839]! } + public var TwoStepAuth_SetPasswordHelp: String { return self._s[1840]! } + public var Notifications_Reset: String { return self._s[1841]! } + public var Conversation_Pin: String { return self._s[1842]! } + public var Passport_Language_lv: String { return self._s[1843]! } + public var Permissions_PeopleNearbyAllowInSettings_v0: String { return self._s[1844]! } + public var BlockedUsers_Info: String { return self._s[1845]! } + public var SettingsSearch_Synonyms_Data_AutoplayVideos: String { return self._s[1847]! } + public var Watch_Conversation_Unblock: String { return self._s[1849]! } public func Time_MonthOfYear_m9(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1841]!, self._r[1841]!, [_0]) + return formatWithArgumentRanges(self._s[1850]!, self._r[1850]!, [_0]) } - public var CloudStorage_Title: String { return self._s[1842]! } - public var GroupInfo_DeleteAndExitConfirmation: String { return self._s[1843]! } + public var CloudStorage_Title: String { return self._s[1851]! } + public var GroupInfo_DeleteAndExitConfirmation: String { return self._s[1852]! } public func NetworkUsageSettings_WifiUsageSince(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1844]!, self._r[1844]!, [_0]) + return formatWithArgumentRanges(self._s[1853]!, self._r[1853]!, [_0]) } - public var Channel_AdminLogFilter_AdminsTitle: String { return self._s[1845]! } - public var Watch_Suggestion_OnMyWay: String { return self._s[1846]! } - public var TwoStepAuth_RecoveryEmailTitle: String { return self._s[1847]! } - public var Passport_Address_EditBankStatement: String { return self._s[1848]! } + public var Channel_AdminLogFilter_AdminsTitle: String { return self._s[1854]! } + public var Watch_Suggestion_OnMyWay: String { return self._s[1855]! } + public var TwoStepAuth_RecoveryEmailTitle: String { return self._s[1856]! } + public var Passport_Address_EditBankStatement: String { return self._s[1857]! } public func Channel_AdminLog_MessageChangedUnlinkedGroup(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1849]!, self._r[1849]!, [_1, _2]) + return formatWithArgumentRanges(self._s[1858]!, self._r[1858]!, [_1, _2]) } - public var ChatSettings_DownloadInBackgroundInfo: String { return self._s[1850]! } - public var ShareMenu_Comment: String { return self._s[1851]! } - public var Permissions_ContactsTitle_v0: String { return self._s[1852]! } - public var Notifications_PermissionsTitle: String { return self._s[1853]! } - public var GroupPermission_NoSendLinks: String { return self._s[1854]! } - public var Privacy_Forwards_NeverAllow_Title: String { return self._s[1855]! } - public var Wallet_SecureStorageChanged_ImportWallet: String { return self._s[1856]! } - public var PeerInfo_PaneLinks: String { return self._s[1857]! } - public var Settings_Support: String { return self._s[1858]! } - public var Notifications_ChannelNotificationsSound: String { return self._s[1859]! } - public var SettingsSearch_Synonyms_Data_AutoDownloadReset: String { return self._s[1860]! } - public var Privacy_Forwards_Preview: String { return self._s[1861]! } - public var GroupPermission_ApplyAlertAction: String { return self._s[1862]! } - public var Watch_Stickers_StickerPacks: String { return self._s[1863]! } - public var Common_Select: String { return self._s[1865]! } - public var CheckoutInfo_ErrorEmailInvalid: String { return self._s[1866]! } - public var WallpaperSearch_ColorGray: String { return self._s[1869]! } - public var TwoFactorSetup_Password_PlaceholderPassword: String { return self._s[1870]! } - public var TwoFactorSetup_Hint_SkipAction: String { return self._s[1871]! } - public var ChatAdmins_AllMembersAreAdminsOffHelp: String { return self._s[1872]! } - public var PollResults_Title: String { return self._s[1873]! } - public var PasscodeSettings_AutoLock_IfAwayFor_5hours: String { return self._s[1874]! } - public var Appearance_PreviewReplyAuthor: String { return self._s[1875]! } - public var TwoStepAuth_RecoveryTitle: String { return self._s[1876]! } - public var Widget_AuthRequired: String { return self._s[1877]! } - public var Camera_FlashOn: String { return self._s[1878]! } - public var Conversation_ContextMenuLookUp: String { return self._s[1879]! } - public var Channel_Stickers_NotFoundHelp: String { return self._s[1880]! } - public var Watch_Suggestion_OK: String { return self._s[1881]! } + public var ChatSettings_DownloadInBackgroundInfo: String { return self._s[1859]! } + public var ShareMenu_Comment: String { return self._s[1860]! } + public var Permissions_ContactsTitle_v0: String { return self._s[1861]! } + public var Notifications_PermissionsTitle: String { return self._s[1862]! } + public var GroupPermission_NoSendLinks: String { return self._s[1863]! } + public var Privacy_Forwards_NeverAllow_Title: String { return self._s[1864]! } + public var Wallet_SecureStorageChanged_ImportWallet: String { return self._s[1865]! } + public var PeerInfo_PaneLinks: String { return self._s[1866]! } + public var Settings_Support: String { return self._s[1867]! } + public var Notifications_ChannelNotificationsSound: String { return self._s[1868]! } + public var SettingsSearch_Synonyms_Data_AutoDownloadReset: String { return self._s[1869]! } + public var Privacy_Forwards_Preview: String { return self._s[1870]! } + public var GroupPermission_ApplyAlertAction: String { return self._s[1871]! } + public var Watch_Stickers_StickerPacks: String { return self._s[1872]! } + public var Common_Select: String { return self._s[1874]! } + public var CheckoutInfo_ErrorEmailInvalid: String { return self._s[1875]! } + public var WallpaperSearch_ColorGray: String { return self._s[1878]! } + public var TwoFactorSetup_Password_PlaceholderPassword: String { return self._s[1879]! } + public var TwoFactorSetup_Hint_SkipAction: String { return self._s[1880]! } + public var ChatAdmins_AllMembersAreAdminsOffHelp: String { return self._s[1881]! } + public var PollResults_Title: String { return self._s[1882]! } + public var PasscodeSettings_AutoLock_IfAwayFor_5hours: String { return self._s[1883]! } + public var Appearance_PreviewReplyAuthor: String { return self._s[1884]! } + public var TwoStepAuth_RecoveryTitle: String { return self._s[1885]! } + public var Widget_AuthRequired: String { return self._s[1886]! } + public var Camera_FlashOn: String { return self._s[1887]! } + public var Conversation_ContextMenuLookUp: String { return self._s[1888]! } + public var Channel_Stickers_NotFoundHelp: String { return self._s[1889]! } + public var Watch_Suggestion_OK: String { return self._s[1890]! } public func Username_LinkHint(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1883]!, self._r[1883]!, [_0]) + return formatWithArgumentRanges(self._s[1892]!, self._r[1892]!, [_0]) } public func Notification_PinnedLiveLocationMessage(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1885]!, self._r[1885]!, [_0]) + return formatWithArgumentRanges(self._s[1894]!, self._r[1894]!, [_0]) } - public var TextFormat_Strikethrough: String { return self._s[1886]! } - public var DialogList_AdLabel: String { return self._s[1887]! } - public var WatchRemote_NotificationText: String { return self._s[1888]! } - public var IntentsSettings_SuggestedChatsSavedMessages: String { return self._s[1889]! } - public var SettingsSearch_Synonyms_Notifications_MessageNotificationsAlert: String { return self._s[1890]! } - public var Conversation_ReportSpam: String { return self._s[1891]! } - public var SettingsSearch_Synonyms_Privacy_Data_TopPeers: String { return self._s[1892]! } - public var Settings_LogoutConfirmationTitle: String { return self._s[1894]! } - public var PhoneLabel_Title: String { return self._s[1895]! } - public var Passport_Address_EditRentalAgreement: String { return self._s[1896]! } - public var Settings_ChangePhoneNumber: String { return self._s[1897]! } - public var Notifications_ExceptionsTitle: String { return self._s[1898]! } - public var Notifications_AlertTones: String { return self._s[1899]! } - public var Call_ReportIncludeLogDescription: String { return self._s[1900]! } - public var SettingsSearch_Synonyms_Notifications_ResetAllNotifications: String { return self._s[1901]! } - public var AutoDownloadSettings_PrivateChats: String { return self._s[1902]! } - public var VoiceOver_Chat_Photo: String { return self._s[1904]! } - public var TwoStepAuth_AddHintTitle: String { return self._s[1905]! } - public var ReportPeer_ReasonOther: String { return self._s[1906]! } - public var ChatList_Context_JoinChannel: String { return self._s[1907]! } - public var KeyCommand_ScrollDown: String { return self._s[1909]! } - public var Conversation_ScheduleMessage_Title: String { return self._s[1910]! } + public var TextFormat_Strikethrough: String { return self._s[1895]! } + public var DialogList_AdLabel: String { return self._s[1896]! } + public var WatchRemote_NotificationText: String { return self._s[1897]! } + public var IntentsSettings_SuggestedChatsSavedMessages: String { return self._s[1898]! } + public var SettingsSearch_Synonyms_Notifications_MessageNotificationsAlert: String { return self._s[1899]! } + public var Conversation_ReportSpam: String { return self._s[1900]! } + public var SettingsSearch_Synonyms_Privacy_Data_TopPeers: String { return self._s[1901]! } + public var Settings_LogoutConfirmationTitle: String { return self._s[1903]! } + public var PhoneLabel_Title: String { return self._s[1904]! } + public var Passport_Address_EditRentalAgreement: String { return self._s[1905]! } + public var Settings_ChangePhoneNumber: String { return self._s[1906]! } + public var Notifications_ExceptionsTitle: String { return self._s[1907]! } + public var Notifications_AlertTones: String { return self._s[1908]! } + public var Call_ReportIncludeLogDescription: String { return self._s[1909]! } + public var SettingsSearch_Synonyms_Notifications_ResetAllNotifications: String { return self._s[1910]! } + public var AutoDownloadSettings_PrivateChats: String { return self._s[1911]! } + public var VoiceOver_Chat_Photo: String { return self._s[1913]! } + public var TwoStepAuth_AddHintTitle: String { return self._s[1914]! } + public var ReportPeer_ReasonOther: String { return self._s[1915]! } + public var ChatList_Context_JoinChannel: String { return self._s[1916]! } + public var KeyCommand_ScrollDown: String { return self._s[1918]! } + public var Conversation_ScheduleMessage_Title: String { return self._s[1919]! } public func Login_BannedPhoneSubject(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1911]!, self._r[1911]!, [_0]) - } - public var NetworkUsageSettings_MediaVideoDataSection: String { return self._s[1912]! } - public var ChannelInfo_DeleteGroupConfirmation: String { return self._s[1913]! } - public var AuthSessions_LogOut: String { return self._s[1914]! } - public var Passport_Identity_TypeInternalPassport: String { return self._s[1915]! } - public var ChatSettings_AutoDownloadVoiceMessages: String { return self._s[1916]! } - public var Passport_Phone_Title: String { return self._s[1917]! } - public var ContactList_Context_StartSecretChat: String { return self._s[1918]! } - public var Settings_PhoneNumber: String { return self._s[1919]! } - public func Conversation_ScheduleMessage_SendToday(_ _0: String) -> (String, [(Int, NSRange)]) { return formatWithArgumentRanges(self._s[1920]!, self._r[1920]!, [_0]) } - public var NotificationsSound_Alert: String { return self._s[1922]! } - public var Wallet_SecureStorageChanged_CreateWallet: String { return self._s[1923]! } - public var WebSearch_SearchNoResults: String { return self._s[1924]! } - public var Privacy_ProfilePhoto_AlwaysShareWith_Title: String { return self._s[1926]! } - public var Wallet_Configuration_SourceInfo: String { return self._s[1927]! } - public var LogoutOptions_AlternativeOptionsSection: String { return self._s[1928]! } - public var SettingsSearch_Synonyms_Passport: String { return self._s[1929]! } - public var PhotoEditor_CurvesTool: String { return self._s[1930]! } - public var Checkout_PaymentMethod: String { return self._s[1932]! } + public var NetworkUsageSettings_MediaVideoDataSection: String { return self._s[1921]! } + public var ChannelInfo_DeleteGroupConfirmation: String { return self._s[1922]! } + public var AuthSessions_LogOut: String { return self._s[1923]! } + public var Passport_Identity_TypeInternalPassport: String { return self._s[1924]! } + public var ChatSettings_AutoDownloadVoiceMessages: String { return self._s[1925]! } + public var Passport_Phone_Title: String { return self._s[1926]! } + public var ContactList_Context_StartSecretChat: String { return self._s[1927]! } + public var Settings_PhoneNumber: String { return self._s[1928]! } + public func Conversation_ScheduleMessage_SendToday(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1929]!, self._r[1929]!, [_0]) + } + public var NotificationsSound_Alert: String { return self._s[1931]! } + public var Wallet_SecureStorageChanged_CreateWallet: String { return self._s[1932]! } + public var WebSearch_SearchNoResults: String { return self._s[1933]! } + public var Privacy_ProfilePhoto_AlwaysShareWith_Title: String { return self._s[1935]! } + public var Wallet_Configuration_SourceInfo: String { return self._s[1936]! } + public var LogoutOptions_AlternativeOptionsSection: String { return self._s[1937]! } + public var SettingsSearch_Synonyms_Passport: String { return self._s[1938]! } + public var PhotoEditor_CurvesTool: String { return self._s[1939]! } + public var Checkout_PaymentMethod: String { return self._s[1941]! } public func PUSH_CHAT_ADD_YOU(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1933]!, self._r[1933]!, [_1, _2]) + return formatWithArgumentRanges(self._s[1942]!, self._r[1942]!, [_1, _2]) } - public var Contacts_AccessDeniedError: String { return self._s[1934]! } - public var Camera_PhotoMode: String { return self._s[1937]! } - public var EditTheme_Expand_Preview_IncomingText: String { return self._s[1938]! } - public var Appearance_TextSize_Apply: String { return self._s[1939]! } - public var Passport_Address_AddUtilityBill: String { return self._s[1941]! } - public var CallSettings_OnMobile: String { return self._s[1942]! } - public var Tour_Text2: String { return self._s[1943]! } + public var Contacts_AccessDeniedError: String { return self._s[1943]! } + public var Camera_PhotoMode: String { return self._s[1946]! } + public var EditTheme_Expand_Preview_IncomingText: String { return self._s[1947]! } + public var Appearance_TextSize_Apply: String { return self._s[1948]! } + public var Passport_Address_AddUtilityBill: String { return self._s[1950]! } + public var CallSettings_OnMobile: String { return self._s[1951]! } + public var Tour_Text2: String { return self._s[1952]! } public func PUSH_CHAT_MESSAGE_ROUND(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1944]!, self._r[1944]!, [_1, _2]) + return formatWithArgumentRanges(self._s[1953]!, self._r[1953]!, [_1, _2]) } - public var DialogList_EncryptionProcessing: String { return self._s[1946]! } - public var Permissions_Skip: String { return self._s[1947]! } - public var Wallet_Words_NotDoneOk: String { return self._s[1948]! } - public var SecretImage_Title: String { return self._s[1949]! } - public var Watch_MessageView_Title: String { return self._s[1950]! } - public var Channel_DiscussionGroupAdd: String { return self._s[1951]! } - public var AttachmentMenu_Poll: String { return self._s[1952]! } + public var DialogList_EncryptionProcessing: String { return self._s[1955]! } + public var Permissions_Skip: String { return self._s[1956]! } + public var Wallet_Words_NotDoneOk: String { return self._s[1957]! } + public var SecretImage_Title: String { return self._s[1958]! } + public var Watch_MessageView_Title: String { return self._s[1959]! } + public var Channel_DiscussionGroupAdd: String { return self._s[1960]! } + public var AttachmentMenu_Poll: String { return self._s[1961]! } public func Notification_GroupInviter(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1953]!, self._r[1953]!, [_0]) + return formatWithArgumentRanges(self._s[1962]!, self._r[1962]!, [_0]) } public func Channel_DiscussionGroup_PrivateChannelLink(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1954]!, self._r[1954]!, [_1, _2]) + return formatWithArgumentRanges(self._s[1963]!, self._r[1963]!, [_1, _2]) } - public var Notification_CallCanceled: String { return self._s[1955]! } - public var WallpaperPreview_Title: String { return self._s[1956]! } - public var Privacy_PaymentsClear_PaymentInfo: String { return self._s[1957]! } - public var Settings_ProxyConnecting: String { return self._s[1958]! } - public var Settings_CheckPhoneNumberText: String { return self._s[1960]! } - public var VoiceOver_Chat_YourVideo: String { return self._s[1961]! } - public var Wallet_Intro_Title: String { return self._s[1962]! } - public var TwoFactorSetup_Password_Action: String { return self._s[1963]! } - public var Profile_MessageLifetime5s: String { return self._s[1964]! } - public var Username_InvalidCharacters: String { return self._s[1965]! } - public var VoiceOver_Media_PlaybackRateFast: String { return self._s[1966]! } - public var ScheduledMessages_ClearAll: String { return self._s[1967]! } - public var WallpaperPreview_CropBottomText: String { return self._s[1968]! } - public var AutoDownloadSettings_LimitBySize: String { return self._s[1969]! } - public var Settings_AddAccount: String { return self._s[1970]! } - public var Notification_CreatedChannel: String { return self._s[1973]! } + public var Notification_CallCanceled: String { return self._s[1964]! } + public var WallpaperPreview_Title: String { return self._s[1965]! } + public var Privacy_PaymentsClear_PaymentInfo: String { return self._s[1966]! } + public var Settings_ProxyConnecting: String { return self._s[1967]! } + public var Settings_CheckPhoneNumberText: String { return self._s[1969]! } + public var VoiceOver_Chat_YourVideo: String { return self._s[1970]! } + public var Wallet_Intro_Title: String { return self._s[1971]! } + public var TwoFactorSetup_Password_Action: String { return self._s[1972]! } + public var Profile_MessageLifetime5s: String { return self._s[1973]! } + public var Username_InvalidCharacters: String { return self._s[1974]! } + public var VoiceOver_Media_PlaybackRateFast: String { return self._s[1975]! } + public var ScheduledMessages_ClearAll: String { return self._s[1976]! } + public var WallpaperPreview_CropBottomText: String { return self._s[1977]! } + public var AutoDownloadSettings_LimitBySize: String { return self._s[1978]! } + public var Settings_AddAccount: String { return self._s[1979]! } + public var Notification_CreatedChannel: String { return self._s[1982]! } public func PUSH_CHAT_DELETE_MEMBER(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1974]!, self._r[1974]!, [_1, _2, _3]) + return formatWithArgumentRanges(self._s[1983]!, self._r[1983]!, [_1, _2, _3]) } - public var Passcode_AppLockedAlert: String { return self._s[1976]! } - public var StickerPacksSettings_AnimatedStickersInfo: String { return self._s[1977]! } - public var VoiceOver_Media_PlaybackStop: String { return self._s[1978]! } - public var Contacts_TopSection: String { return self._s[1979]! } - public var ChatList_DeleteForEveryoneConfirmationAction: String { return self._s[1980]! } + public var Passcode_AppLockedAlert: String { return self._s[1985]! } + public var StickerPacksSettings_AnimatedStickersInfo: String { return self._s[1986]! } + public var VoiceOver_Media_PlaybackStop: String { return self._s[1987]! } + public var Contacts_TopSection: String { return self._s[1988]! } + public var ChatList_DeleteForEveryoneConfirmationAction: String { return self._s[1989]! } public func Conversation_SetReminder_RemindOn(_ _0: String, _ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1981]!, self._r[1981]!, [_0, _1]) + return formatWithArgumentRanges(self._s[1990]!, self._r[1990]!, [_0, _1]) } - public var Wallet_Info_Receive: String { return self._s[1982]! } - public var Wallet_Completed_ViewWallet: String { return self._s[1983]! } + public var Wallet_Info_Receive: String { return self._s[1991]! } + public var Wallet_Completed_ViewWallet: String { return self._s[1992]! } public func Time_MonthOfYear_m6(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1984]!, self._r[1984]!, [_0]) + return formatWithArgumentRanges(self._s[1993]!, self._r[1993]!, [_0]) } - public var ReportPeer_ReasonSpam: String { return self._s[1985]! } - public var UserInfo_TapToCall: String { return self._s[1986]! } - public var Conversation_ForwardAuthorHiddenTooltip: String { return self._s[1988]! } - public var AutoDownloadSettings_DataUsageCustom: String { return self._s[1989]! } - public var Common_Search: String { return self._s[1990]! } - public var ScheduledMessages_EmptyPlaceholder: String { return self._s[1991]! } + public var ReportPeer_ReasonSpam: String { return self._s[1994]! } + public var UserInfo_TapToCall: String { return self._s[1995]! } + public var Conversation_ForwardAuthorHiddenTooltip: String { return self._s[1997]! } + public var AutoDownloadSettings_DataUsageCustom: String { return self._s[1998]! } + public var Common_Search: String { return self._s[1999]! } + public var ScheduledMessages_EmptyPlaceholder: String { return self._s[2000]! } public func Channel_AdminLog_MessageChangedGroupGeoLocation(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1992]!, self._r[1992]!, [_0]) + return formatWithArgumentRanges(self._s[2001]!, self._r[2001]!, [_0]) } - public var Wallet_Month_ShortJuly: String { return self._s[1993]! } - public var AuthSessions_IncompleteAttemptsInfo: String { return self._s[1995]! } - public var Message_InvoiceLabel: String { return self._s[1996]! } - public var Conversation_InputTextPlaceholder: String { return self._s[1997]! } - public var NetworkUsageSettings_MediaImageDataSection: String { return self._s[1998]! } + public var Wallet_Month_ShortJuly: String { return self._s[2002]! } + public var AuthSessions_IncompleteAttemptsInfo: String { return self._s[2004]! } + public var Message_InvoiceLabel: String { return self._s[2005]! } + public var Conversation_InputTextPlaceholder: String { return self._s[2006]! } + public var NetworkUsageSettings_MediaImageDataSection: String { return self._s[2007]! } public func Passport_Address_UploadOneOfScan(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1999]!, self._r[1999]!, [_0]) + return formatWithArgumentRanges(self._s[2008]!, self._r[2008]!, [_0]) } - public var IntentsSettings_Reset: String { return self._s[2000]! } - public var Conversation_Info: String { return self._s[2001]! } - public var Login_InfoDeletePhoto: String { return self._s[2002]! } - public var Passport_Language_vi: String { return self._s[2004]! } - public var UserInfo_ScamUserWarning: String { return self._s[2005]! } - public var Conversation_Search: String { return self._s[2006]! } - public var DialogList_DeleteBotConversationConfirmation: String { return self._s[2008]! } - public var ReportPeer_ReasonPornography: String { return self._s[2009]! } - public var AutoDownloadSettings_PhotosTitle: String { return self._s[2010]! } - public var Conversation_SendMessageErrorGroupRestricted: String { return self._s[2011]! } - public var Map_LiveLocationGroupDescription: String { return self._s[2012]! } - public var Channel_Setup_TypeHeader: String { return self._s[2013]! } - public var AuthSessions_LoggedIn: String { return self._s[2014]! } - public var Privacy_Forwards_AlwaysAllow_Title: String { return self._s[2015]! } - public var Login_SmsRequestState3: String { return self._s[2016]! } - public var Passport_Address_EditUtilityBill: String { return self._s[2017]! } - public var Appearance_ReduceMotionInfo: String { return self._s[2018]! } - public var Join_ChannelsTooMuch: String { return self._s[2019]! } - public var Channel_Edit_LinkItem: String { return self._s[2020]! } - public var Privacy_Calls_P2PNever: String { return self._s[2021]! } - public var Conversation_AddToReadingList: String { return self._s[2023]! } - public var Share_MultipleMessagesDisabled: String { return self._s[2024]! } - public var Message_Animation: String { return self._s[2025]! } - public var Conversation_DefaultRestrictedMedia: String { return self._s[2026]! } - public var Map_Unknown: String { return self._s[2027]! } - public var AutoDownloadSettings_LastDelimeter: String { return self._s[2028]! } + public var IntentsSettings_Reset: String { return self._s[2009]! } + public var Conversation_Info: String { return self._s[2010]! } + public var Login_InfoDeletePhoto: String { return self._s[2011]! } + public var Passport_Language_vi: String { return self._s[2013]! } + public var UserInfo_ScamUserWarning: String { return self._s[2014]! } + public var Conversation_Search: String { return self._s[2015]! } + public var DialogList_DeleteBotConversationConfirmation: String { return self._s[2017]! } + public var ReportPeer_ReasonPornography: String { return self._s[2018]! } + public var AutoDownloadSettings_PhotosTitle: String { return self._s[2019]! } + public var Conversation_SendMessageErrorGroupRestricted: String { return self._s[2020]! } + public var Map_LiveLocationGroupDescription: String { return self._s[2021]! } + public var Channel_Setup_TypeHeader: String { return self._s[2022]! } + public var AuthSessions_LoggedIn: String { return self._s[2023]! } + public var Privacy_Forwards_AlwaysAllow_Title: String { return self._s[2024]! } + public var Login_SmsRequestState3: String { return self._s[2025]! } + public var Passport_Address_EditUtilityBill: String { return self._s[2026]! } + public var Appearance_ReduceMotionInfo: String { return self._s[2027]! } + public var Join_ChannelsTooMuch: String { return self._s[2028]! } + public var Channel_Edit_LinkItem: String { return self._s[2029]! } + public var Privacy_Calls_P2PNever: String { return self._s[2030]! } + public var Conversation_AddToReadingList: String { return self._s[2032]! } + public var Share_MultipleMessagesDisabled: String { return self._s[2033]! } + public var Message_Animation: String { return self._s[2034]! } + public var Conversation_DefaultRestrictedMedia: String { return self._s[2035]! } + public var Map_Unknown: String { return self._s[2036]! } + public var AutoDownloadSettings_LastDelimeter: String { return self._s[2037]! } public func PUSH_PINNED_TEXT(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2029]!, self._r[2029]!, [_1, _2]) + return formatWithArgumentRanges(self._s[2038]!, self._r[2038]!, [_1, _2]) } public func Passport_FieldOneOf_Or(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2030]!, self._r[2030]!, [_1, _2]) + return formatWithArgumentRanges(self._s[2039]!, self._r[2039]!, [_1, _2]) } - public var Call_StatusRequesting: String { return self._s[2031]! } - public var Conversation_SecretChatContextBotAlert: String { return self._s[2032]! } - public var SocksProxySetup_ProxyStatusChecking: String { return self._s[2033]! } + public var Call_StatusRequesting: String { return self._s[2040]! } + public var Conversation_SecretChatContextBotAlert: String { return self._s[2041]! } + public var SocksProxySetup_ProxyStatusChecking: String { return self._s[2042]! } public func PUSH_CHAT_MESSAGE_DOC(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2034]!, self._r[2034]!, [_1, _2]) + return formatWithArgumentRanges(self._s[2043]!, self._r[2043]!, [_1, _2]) } public func Notification_PinnedLocationMessage(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2035]!, self._r[2035]!, [_0]) + return formatWithArgumentRanges(self._s[2044]!, self._r[2044]!, [_0]) } - public var Update_Skip: String { return self._s[2036]! } - public var Group_Username_RemoveExistingUsernamesInfo: String { return self._s[2037]! } - public var BlockedUsers_Title: String { return self._s[2038]! } - public var Weekday_Monday: String { return self._s[2039]! } + public var Update_Skip: String { return self._s[2045]! } + public var Group_Username_RemoveExistingUsernamesInfo: String { return self._s[2046]! } + public var BlockedUsers_Title: String { return self._s[2047]! } + public var Weekday_Monday: String { return self._s[2048]! } public func PUSH_CHANNEL_MESSAGE_AUDIO(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2040]!, self._r[2040]!, [_1]) + return formatWithArgumentRanges(self._s[2049]!, self._r[2049]!, [_1]) } - public var Username_CheckingUsername: String { return self._s[2041]! } - public var NotificationsSound_Bell: String { return self._s[2042]! } - public var Conversation_SendMessageErrorFlood: String { return self._s[2043]! } - public var SettingsSearch_Synonyms_Notifications_DisplayNamesOnLockScreen: String { return self._s[2044]! } - public var ChannelMembers_ChannelAdminsTitle: String { return self._s[2045]! } - public var ChatSettings_Groups: String { return self._s[2046]! } - public var WallpaperPreview_PatternPaternDiscard: String { return self._s[2047]! } + public var Username_CheckingUsername: String { return self._s[2050]! } + public var NotificationsSound_Bell: String { return self._s[2051]! } + public var Conversation_SendMessageErrorFlood: String { return self._s[2052]! } + public var SettingsSearch_Synonyms_Notifications_DisplayNamesOnLockScreen: String { return self._s[2053]! } + public var ChannelMembers_ChannelAdminsTitle: String { return self._s[2054]! } + public var ChatSettings_Groups: String { return self._s[2055]! } + public var WallpaperPreview_PatternPaternDiscard: String { return self._s[2056]! } public func Conversation_SetReminder_RemindTomorrow(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2048]!, self._r[2048]!, [_0]) + return formatWithArgumentRanges(self._s[2057]!, self._r[2057]!, [_0]) } - public var Your_card_was_declined: String { return self._s[2049]! } - public var TwoStepAuth_EnterPasswordHelp: String { return self._s[2051]! } - public var Wallet_Month_ShortApril: String { return self._s[2052]! } - public var ChatList_Unmute: String { return self._s[2053]! } - public var AuthSessions_AddDevice_ScanTitle: String { return self._s[2054]! } - public var PhotoEditor_CurvesAll: String { return self._s[2055]! } - public var Weekday_ShortTuesday: String { return self._s[2056]! } - public var DialogList_Read: String { return self._s[2057]! } - public var Appearance_AppIconClassic: String { return self._s[2058]! } - public var ChannelMembers_WhoCanAddMembers_AllMembers: String { return self._s[2059]! } - public var Passport_Identity_Gender: String { return self._s[2060]! } + public var Your_card_was_declined: String { return self._s[2058]! } + public var TwoStepAuth_EnterPasswordHelp: String { return self._s[2060]! } + public var Wallet_Month_ShortApril: String { return self._s[2061]! } + public var ChatList_Unmute: String { return self._s[2062]! } + public var AuthSessions_AddDevice_ScanTitle: String { return self._s[2063]! } + public var PhotoEditor_CurvesAll: String { return self._s[2064]! } + public var Weekday_ShortTuesday: String { return self._s[2065]! } + public var DialogList_Read: String { return self._s[2066]! } + public var Appearance_AppIconClassic: String { return self._s[2067]! } + public var ChannelMembers_WhoCanAddMembers_AllMembers: String { return self._s[2068]! } + public var Passport_Identity_Gender: String { return self._s[2069]! } public func Target_ShareGameConfirmationPrivate(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2061]!, self._r[2061]!, [_0]) + return formatWithArgumentRanges(self._s[2070]!, self._r[2070]!, [_0]) } - public var Target_SelectGroup: String { return self._s[2062]! } - public var Map_HomeAndWorkInfo: String { return self._s[2064]! } + public var Target_SelectGroup: String { return self._s[2071]! } + public var Map_HomeAndWorkInfo: String { return self._s[2073]! } public func DialogList_EncryptedChatStartedIncoming(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2065]!, self._r[2065]!, [_0]) + return formatWithArgumentRanges(self._s[2074]!, self._r[2074]!, [_0]) } - public var Passport_Language_en: String { return self._s[2066]! } - public var AutoDownloadSettings_AutodownloadPhotos: String { return self._s[2067]! } - public var Channel_Username_CreatePublicLinkHelp: String { return self._s[2068]! } - public var Login_CancelPhoneVerificationContinue: String { return self._s[2069]! } - public var ScheduledMessages_SendNow: String { return self._s[2070]! } - public var Checkout_NewCard_PaymentCard: String { return self._s[2072]! } - public var Login_InfoHelp: String { return self._s[2073]! } - public var Appearance_BubbleCorners_AdjustAdjacent: String { return self._s[2074]! } - public var Contacts_PermissionsSuppressWarningTitle: String { return self._s[2075]! } - public var SettingsSearch_Synonyms_Stickers_FeaturedPacks: String { return self._s[2076]! } + public var Passport_Language_en: String { return self._s[2075]! } + public var AutoDownloadSettings_AutodownloadPhotos: String { return self._s[2076]! } + public var Channel_Username_CreatePublicLinkHelp: String { return self._s[2077]! } + public var Login_CancelPhoneVerificationContinue: String { return self._s[2078]! } + public var ScheduledMessages_SendNow: String { return self._s[2079]! } + public var Checkout_NewCard_PaymentCard: String { return self._s[2081]! } + public var Login_InfoHelp: String { return self._s[2082]! } + public var Appearance_BubbleCorners_AdjustAdjacent: String { return self._s[2083]! } + public var Contacts_PermissionsSuppressWarningTitle: String { return self._s[2084]! } + public var SettingsSearch_Synonyms_Stickers_FeaturedPacks: String { return self._s[2085]! } public func Channel_AdminLog_MessageChangedLinkedChannel(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2077]!, self._r[2077]!, [_1, _2]) + return formatWithArgumentRanges(self._s[2086]!, self._r[2086]!, [_1, _2]) } - public var SocksProxySetup_AddProxy: String { return self._s[2080]! } - public var CreatePoll_Title: String { return self._s[2081]! } - public var MessagePoll_QuizNoUsers: String { return self._s[2082]! } - public var Conversation_ViewTheme: String { return self._s[2083]! } - public var SettingsSearch_Synonyms_Privacy_Data_SecretChatLinkPreview: String { return self._s[2084]! } - public var PasscodeSettings_SimplePasscodeHelp: String { return self._s[2085]! } - public var TwoFactorSetup_Intro_Text: String { return self._s[2086]! } - public var UserInfo_GroupsInCommon: String { return self._s[2087]! } - public var TelegramWallet_Intro_TermsUrl: String { return self._s[2088]! } - public var Call_AudioRouteHide: String { return self._s[2089]! } + public var SocksProxySetup_AddProxy: String { return self._s[2089]! } + public var CreatePoll_Title: String { return self._s[2090]! } + public var MessagePoll_QuizNoUsers: String { return self._s[2091]! } + public var Conversation_ViewTheme: String { return self._s[2092]! } + public var SettingsSearch_Synonyms_Privacy_Data_SecretChatLinkPreview: String { return self._s[2093]! } + public var PasscodeSettings_SimplePasscodeHelp: String { return self._s[2094]! } + public var TwoFactorSetup_Intro_Text: String { return self._s[2095]! } + public var UserInfo_GroupsInCommon: String { return self._s[2096]! } + public var TelegramWallet_Intro_TermsUrl: String { return self._s[2097]! } + public var Call_AudioRouteHide: String { return self._s[2098]! } public func Wallet_Info_TransactionDateHeader(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2091]!, self._r[2091]!, [_1, _2]) + return formatWithArgumentRanges(self._s[2100]!, self._r[2100]!, [_1, _2]) } - public var ContactInfo_PhoneLabelMobile: String { return self._s[2092]! } - public var IntentsSettings_SuggestedChatsInfo: String { return self._s[2093]! } - public var CreatePoll_QuizOptionsHeader: String { return self._s[2094]! } + public var ContactInfo_PhoneLabelMobile: String { return self._s[2101]! } + public var IntentsSettings_SuggestedChatsInfo: String { return self._s[2102]! } + public var CreatePoll_QuizOptionsHeader: String { return self._s[2103]! } public func ChatList_LeaveGroupConfirmation(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2095]!, self._r[2095]!, [_0]) + return formatWithArgumentRanges(self._s[2104]!, self._r[2104]!, [_0]) } - public var TextFormat_Bold: String { return self._s[2096]! } - public var FastTwoStepSetup_EmailSection: String { return self._s[2097]! } - public var StickerPackActionInfo_AddedTitle: String { return self._s[2098]! } - public var Notifications_Title: String { return self._s[2099]! } - public var Group_Username_InvalidTooShort: String { return self._s[2100]! } - public var Channel_ErrorAddTooMuch: String { return self._s[2101]! } + public var TextFormat_Bold: String { return self._s[2105]! } + public var FastTwoStepSetup_EmailSection: String { return self._s[2106]! } + public var StickerPackActionInfo_AddedTitle: String { return self._s[2107]! } + public var Notifications_Title: String { return self._s[2108]! } + public var Group_Username_InvalidTooShort: String { return self._s[2109]! } + public var Channel_ErrorAddTooMuch: String { return self._s[2110]! } public func DialogList_MultipleTypingSuffix(_ _0: Int) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2102]!, self._r[2102]!, ["\(_0)"]) + return formatWithArgumentRanges(self._s[2111]!, self._r[2111]!, ["\(_0)"]) } - public var VoiceOver_DiscardPreparedContent: String { return self._s[2104]! } - public var Stickers_SuggestAdded: String { return self._s[2105]! } - public var Login_CountryCode: String { return self._s[2106]! } - public var ChatSettings_AutoPlayVideos: String { return self._s[2107]! } - public var Map_GetDirections: String { return self._s[2108]! } - public var Wallet_Receive_ShareInvoiceUrl: String { return self._s[2109]! } - public var Login_PhoneFloodError: String { return self._s[2110]! } + public var VoiceOver_DiscardPreparedContent: String { return self._s[2113]! } + public var Stickers_SuggestAdded: String { return self._s[2114]! } + public var Login_CountryCode: String { return self._s[2115]! } + public var ChatSettings_AutoPlayVideos: String { return self._s[2116]! } + public var Map_GetDirections: String { return self._s[2117]! } + public var Wallet_Receive_ShareInvoiceUrl: String { return self._s[2118]! } + public var Login_PhoneFloodError: String { return self._s[2119]! } public func Time_MonthOfYear_m3(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2111]!, self._r[2111]!, [_0]) + return formatWithArgumentRanges(self._s[2120]!, self._r[2120]!, [_0]) } public func Wallet_Time_PreciseDate_m10(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2112]!, self._r[2112]!, [_1, _2, _3]) + return formatWithArgumentRanges(self._s[2121]!, self._r[2121]!, [_1, _2, _3]) } - public var IntentsSettings_SuggestedChatsPrivateChats: String { return self._s[2113]! } - public var Settings_SetUsername: String { return self._s[2115]! } - public var Group_Location_ChangeLocation: String { return self._s[2116]! } - public var Notification_GroupInviterSelf: String { return self._s[2117]! } - public var InstantPage_TapToOpenLink: String { return self._s[2118]! } + public var IntentsSettings_SuggestedChatsPrivateChats: String { return self._s[2122]! } + public var Settings_SetUsername: String { return self._s[2124]! } + public var Group_Location_ChangeLocation: String { return self._s[2125]! } + public var Notification_GroupInviterSelf: String { return self._s[2126]! } + public var InstantPage_TapToOpenLink: String { return self._s[2127]! } public func Notification_ChannelInviter(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2119]!, self._r[2119]!, [_0]) + return formatWithArgumentRanges(self._s[2128]!, self._r[2128]!, [_0]) } - public var Watch_Suggestion_TalkLater: String { return self._s[2120]! } - public var SecretChat_Title: String { return self._s[2121]! } - public var Group_UpgradeNoticeText1: String { return self._s[2122]! } - public var AuthSessions_Title: String { return self._s[2123]! } + public var Watch_Suggestion_TalkLater: String { return self._s[2129]! } + public var SecretChat_Title: String { return self._s[2130]! } + public var Group_UpgradeNoticeText1: String { return self._s[2131]! } + public var AuthSessions_Title: String { return self._s[2132]! } public func TextFormat_AddLinkText(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2124]!, self._r[2124]!, [_0]) + return formatWithArgumentRanges(self._s[2133]!, self._r[2133]!, [_0]) } - public var PhotoEditor_CropAuto: String { return self._s[2125]! } - public var Channel_About_Title: String { return self._s[2126]! } - public var Theme_ThemeChanged: String { return self._s[2127]! } - public var FastTwoStepSetup_EmailHelp: String { return self._s[2128]! } + public var PhotoEditor_CropAuto: String { return self._s[2134]! } + public var Channel_About_Title: String { return self._s[2135]! } + public var Theme_ThemeChanged: String { return self._s[2136]! } + public var FastTwoStepSetup_EmailHelp: String { return self._s[2137]! } public func Conversation_Bytes(_ _0: Int) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2130]!, self._r[2130]!, ["\(_0)"]) + return formatWithArgumentRanges(self._s[2139]!, self._r[2139]!, ["\(_0)"]) } - public var VoiceOver_MessageContextReport: String { return self._s[2131]! } - public var Conversation_PinMessageAlert_OnlyPin: String { return self._s[2133]! } - public var Group_Setup_HistoryVisibleHelp: String { return self._s[2134]! } + public var VoiceOver_MessageContextReport: String { return self._s[2140]! } + public var Conversation_PinMessageAlert_OnlyPin: String { return self._s[2142]! } + public var Group_Setup_HistoryVisibleHelp: String { return self._s[2143]! } public func PUSH_MESSAGE_GIF(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2135]!, self._r[2135]!, [_1]) + return formatWithArgumentRanges(self._s[2144]!, self._r[2144]!, [_1]) } public func SharedMedia_SearchNoResultsDescription(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2137]!, self._r[2137]!, [_0]) + return formatWithArgumentRanges(self._s[2146]!, self._r[2146]!, [_0]) } public func TwoStepAuth_RecoveryEmailUnavailable(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2138]!, self._r[2138]!, [_0]) + return formatWithArgumentRanges(self._s[2147]!, self._r[2147]!, [_0]) } - public var Privacy_PaymentsClearInfoHelp: String { return self._s[2139]! } - public var PeopleNearby_DiscoverDescription: String { return self._s[2141]! } - public var Presence_online: String { return self._s[2143]! } - public var PasscodeSettings_Title: String { return self._s[2144]! } - public var Passport_Identity_ExpiryDatePlaceholder: String { return self._s[2145]! } - public var Web_OpenExternal: String { return self._s[2146]! } - public var AutoDownloadSettings_AutoDownload: String { return self._s[2148]! } - public var Channel_OwnershipTransfer_EnterPasswordText: String { return self._s[2149]! } - public var LocalGroup_Title: String { return self._s[2150]! } + public var Privacy_PaymentsClearInfoHelp: String { return self._s[2148]! } + public var PeopleNearby_DiscoverDescription: String { return self._s[2150]! } + public var Presence_online: String { return self._s[2152]! } + public var PasscodeSettings_Title: String { return self._s[2153]! } + public var Passport_Identity_ExpiryDatePlaceholder: String { return self._s[2154]! } + public var Web_OpenExternal: String { return self._s[2155]! } + public var AutoDownloadSettings_AutoDownload: String { return self._s[2157]! } + public var Channel_OwnershipTransfer_EnterPasswordText: String { return self._s[2158]! } + public var LocalGroup_Title: String { return self._s[2159]! } public func AutoNightTheme_AutomaticHelp(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2151]!, self._r[2151]!, [_0]) - } - public var FastTwoStepSetup_PasswordConfirmationPlaceholder: String { return self._s[2152]! } - public var Conversation_StopQuizConfirmation: String { return self._s[2153]! } - public var Map_YouAreHere: String { return self._s[2154]! } - public func AuthSessions_Message(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2155]!, self._r[2155]!, [_0]) - } - public func ChatList_DeleteChatConfirmation(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2156]!, self._r[2156]!, [_0]) - } - public var Theme_Context_ChangeColors: String { return self._s[2157]! } - public var PrivacyLastSeenSettings_AlwaysShareWith: String { return self._s[2158]! } - public var Target_InviteToGroupErrorAlreadyInvited: String { return self._s[2159]! } - public func AuthSessions_AppUnofficial(_ _0: String) -> (String, [(Int, NSRange)]) { return formatWithArgumentRanges(self._s[2160]!, self._r[2160]!, [_0]) } - public func DialogList_LiveLocationSharingTo(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2161]!, self._r[2161]!, [_0]) - } - public var SocksProxySetup_Username: String { return self._s[2162]! } - public var Bot_Start: String { return self._s[2163]! } - public func Channel_AdminLog_EmptyFilterQueryText(_ _0: String) -> (String, [(Int, NSRange)]) { + public var FastTwoStepSetup_PasswordConfirmationPlaceholder: String { return self._s[2161]! } + public var Conversation_StopQuizConfirmation: String { return self._s[2162]! } + public var Map_YouAreHere: String { return self._s[2163]! } + public func AuthSessions_Message(_ _0: String) -> (String, [(Int, NSRange)]) { return formatWithArgumentRanges(self._s[2164]!, self._r[2164]!, [_0]) } - public func Channel_AdminLog_MessagePinned(_ _0: String) -> (String, [(Int, NSRange)]) { + public func ChatList_DeleteChatConfirmation(_ _0: String) -> (String, [(Int, NSRange)]) { return formatWithArgumentRanges(self._s[2165]!, self._r[2165]!, [_0]) } - public var Contacts_SortByPresence: String { return self._s[2166]! } - public var AccentColor_Title: String { return self._s[2168]! } - public var Conversation_DiscardVoiceMessageTitle: String { return self._s[2169]! } + public var Theme_Context_ChangeColors: String { return self._s[2166]! } + public var PrivacyLastSeenSettings_AlwaysShareWith: String { return self._s[2167]! } + public var Target_InviteToGroupErrorAlreadyInvited: String { return self._s[2168]! } + public func AuthSessions_AppUnofficial(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2169]!, self._r[2169]!, [_0]) + } + public func DialogList_LiveLocationSharingTo(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2170]!, self._r[2170]!, [_0]) + } + public var SocksProxySetup_Username: String { return self._s[2171]! } + public var Bot_Start: String { return self._s[2172]! } + public func Channel_AdminLog_EmptyFilterQueryText(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2173]!, self._r[2173]!, [_0]) + } + public func Channel_AdminLog_MessagePinned(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2174]!, self._r[2174]!, [_0]) + } + public var Contacts_SortByPresence: String { return self._s[2175]! } + public var AccentColor_Title: String { return self._s[2177]! } + public var Conversation_DiscardVoiceMessageTitle: String { return self._s[2178]! } public func PUSH_CHAT_CREATED(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2170]!, self._r[2170]!, [_1, _2]) + return formatWithArgumentRanges(self._s[2179]!, self._r[2179]!, [_1, _2]) } public func PrivacySettings_LastSeenContactsMinus(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2171]!, self._r[2171]!, [_0]) + return formatWithArgumentRanges(self._s[2180]!, self._r[2180]!, [_0]) } public func Channel_AdminLog_MessageChangedLinkedGroup(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2172]!, self._r[2172]!, [_1, _2]) + return formatWithArgumentRanges(self._s[2181]!, self._r[2181]!, [_1, _2]) } - public var Passport_Email_EnterOtherEmail: String { return self._s[2173]! } - public var Login_InfoAvatarPhoto: String { return self._s[2174]! } - public var Privacy_PaymentsClear_ShippingInfo: String { return self._s[2175]! } - public var Tour_Title4: String { return self._s[2176]! } - public var Passport_Identity_Translation: String { return self._s[2177]! } - public var SettingsSearch_Synonyms_Notifications_ContactJoined: String { return self._s[2178]! } - public var Login_TermsOfServiceLabel: String { return self._s[2180]! } - public var Passport_Language_it: String { return self._s[2181]! } - public var KeyCommand_JumpToNextUnreadChat: String { return self._s[2182]! } - public var Passport_Identity_SelfieHelp: String { return self._s[2183]! } - public var Conversation_ClearAll: String { return self._s[2185]! } - public var Wallet_Send_UninitializedText: String { return self._s[2187]! } - public var Channel_OwnershipTransfer_Title: String { return self._s[2188]! } - public var TwoStepAuth_FloodError: String { return self._s[2189]! } + public var Passport_Email_EnterOtherEmail: String { return self._s[2182]! } + public var Login_InfoAvatarPhoto: String { return self._s[2183]! } + public var Privacy_PaymentsClear_ShippingInfo: String { return self._s[2184]! } + public var Tour_Title4: String { return self._s[2185]! } + public var Passport_Identity_Translation: String { return self._s[2186]! } + public var SettingsSearch_Synonyms_Notifications_ContactJoined: String { return self._s[2187]! } + public var Login_TermsOfServiceLabel: String { return self._s[2189]! } + public var Passport_Language_it: String { return self._s[2190]! } + public var KeyCommand_JumpToNextUnreadChat: String { return self._s[2191]! } + public var Passport_Identity_SelfieHelp: String { return self._s[2192]! } + public var Conversation_ClearAll: String { return self._s[2194]! } + public var Wallet_Send_UninitializedText: String { return self._s[2196]! } + public var Channel_OwnershipTransfer_Title: String { return self._s[2197]! } + public var TwoStepAuth_FloodError: String { return self._s[2198]! } public func PUSH_CHANNEL_MESSAGE_GEO(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2190]!, self._r[2190]!, [_1]) + return formatWithArgumentRanges(self._s[2199]!, self._r[2199]!, [_1]) } - public var Paint_Delete: String { return self._s[2191]! } + public var Paint_Delete: String { return self._s[2200]! } public func Wallet_Sent_Text(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2192]!, self._r[2192]!, [_0]) + return formatWithArgumentRanges(self._s[2201]!, self._r[2201]!, [_0]) } - public var Privacy_AddNewPeer: String { return self._s[2193]! } + public var Privacy_AddNewPeer: String { return self._s[2202]! } public func Channel_AdminLog_MessageRank(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2194]!, self._r[2194]!, [_1]) + return formatWithArgumentRanges(self._s[2203]!, self._r[2203]!, [_1]) } - public var LogoutOptions_SetPasscodeText: String { return self._s[2195]! } + public var LogoutOptions_SetPasscodeText: String { return self._s[2204]! } public func Passport_AcceptHelp(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2196]!, self._r[2196]!, [_1, _2]) + return formatWithArgumentRanges(self._s[2205]!, self._r[2205]!, [_1, _2]) } - public var Message_PinnedAudioMessage: String { return self._s[2197]! } + public var Message_PinnedAudioMessage: String { return self._s[2206]! } public func Watch_Time_ShortTodayAt(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2198]!, self._r[2198]!, [_0]) + return formatWithArgumentRanges(self._s[2207]!, self._r[2207]!, [_0]) } - public var Notification_Mute1hMin: String { return self._s[2199]! } - public var Notifications_GroupNotificationsSound: String { return self._s[2200]! } - public var Wallet_Month_GenNovember: String { return self._s[2201]! } - public var SocksProxySetup_ShareProxyList: String { return self._s[2202]! } - public var Conversation_MessageEditedLabel: String { return self._s[2203]! } + public var Notification_Mute1hMin: String { return self._s[2208]! } + public var Notifications_GroupNotificationsSound: String { return self._s[2209]! } + public var Wallet_Month_GenNovember: String { return self._s[2210]! } + public var SocksProxySetup_ShareProxyList: String { return self._s[2211]! } + public var Conversation_MessageEditedLabel: String { return self._s[2212]! } public func ClearCache_Success(_ _0: String, _ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2204]!, self._r[2204]!, [_0, _1]) + return formatWithArgumentRanges(self._s[2213]!, self._r[2213]!, [_0, _1]) } - public var Notification_Exceptions_AlwaysOff: String { return self._s[2205]! } - public var Notification_Exceptions_NewException_MessagePreviewHeader: String { return self._s[2206]! } + public var Notification_Exceptions_AlwaysOff: String { return self._s[2214]! } + public var Notification_Exceptions_NewException_MessagePreviewHeader: String { return self._s[2215]! } public func Channel_AdminLog_MessageAdmin(_ _0: String, _ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2207]!, self._r[2207]!, [_0, _1, _2]) + return formatWithArgumentRanges(self._s[2216]!, self._r[2216]!, [_0, _1, _2]) } - public var NetworkUsageSettings_ResetStats: String { return self._s[2208]! } + public var NetworkUsageSettings_ResetStats: String { return self._s[2217]! } public func PUSH_MESSAGE_GEOLIVE(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2209]!, self._r[2209]!, [_1]) + return formatWithArgumentRanges(self._s[2218]!, self._r[2218]!, [_1]) } - public var AccessDenied_LocationTracking: String { return self._s[2210]! } - public var Month_GenOctober: String { return self._s[2211]! } - public var GroupInfo_InviteLink_RevokeAlert_Revoke: String { return self._s[2212]! } - public var EnterPasscode_EnterPasscode: String { return self._s[2213]! } - public var MediaPicker_TimerTooltip: String { return self._s[2215]! } - public var SharedMedia_TitleAll: String { return self._s[2216]! } - public var SettingsSearch_Synonyms_Notifications_ChannelNotificationsExceptions: String { return self._s[2219]! } - public var Conversation_RestrictedMedia: String { return self._s[2220]! } - public var AccessDenied_PhotosRestricted: String { return self._s[2221]! } - public var Privacy_Forwards_WhoCanForward: String { return self._s[2223]! } - public var ChangePhoneNumberCode_Called: String { return self._s[2224]! } + public var AccessDenied_LocationTracking: String { return self._s[2219]! } + public var Month_GenOctober: String { return self._s[2220]! } + public var GroupInfo_InviteLink_RevokeAlert_Revoke: String { return self._s[2221]! } + public var EnterPasscode_EnterPasscode: String { return self._s[2222]! } + public var MediaPicker_TimerTooltip: String { return self._s[2224]! } + public var SharedMedia_TitleAll: String { return self._s[2225]! } + public var SettingsSearch_Synonyms_Notifications_ChannelNotificationsExceptions: String { return self._s[2228]! } + public var Conversation_RestrictedMedia: String { return self._s[2229]! } + public var AccessDenied_PhotosRestricted: String { return self._s[2230]! } + public var Privacy_Forwards_WhoCanForward: String { return self._s[2232]! } + public var ChangePhoneNumberCode_Called: String { return self._s[2233]! } public func Notification_PinnedDocumentMessage(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2225]!, self._r[2225]!, [_0]) + return formatWithArgumentRanges(self._s[2234]!, self._r[2234]!, [_0]) } - public var Conversation_SavedMessages: String { return self._s[2228]! } - public var Your_cards_expiration_month_is_invalid: String { return self._s[2230]! } - public var FastTwoStepSetup_PasswordPlaceholder: String { return self._s[2231]! } + public var Conversation_SavedMessages: String { return self._s[2237]! } + public var Your_cards_expiration_month_is_invalid: String { return self._s[2239]! } + public var FastTwoStepSetup_PasswordPlaceholder: String { return self._s[2240]! } public func Target_ShareGameConfirmationGroup(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2233]!, self._r[2233]!, [_0]) + return formatWithArgumentRanges(self._s[2242]!, self._r[2242]!, [_0]) } - public var VoiceOver_Chat_YourMessage: String { return self._s[2234]! } + public var VoiceOver_Chat_YourMessage: String { return self._s[2243]! } public func VoiceOver_Chat_Title(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2235]!, self._r[2235]!, [_0]) + return formatWithArgumentRanges(self._s[2244]!, self._r[2244]!, [_0]) } - public var ReportPeer_AlertSuccess: String { return self._s[2236]! } - public var PhotoEditor_CropAspectRatioOriginal: String { return self._s[2237]! } + public var ReportPeer_AlertSuccess: String { return self._s[2245]! } + public var PhotoEditor_CropAspectRatioOriginal: String { return self._s[2246]! } public func InstantPage_RelatedArticleAuthorAndDateTitle(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2238]!, self._r[2238]!, [_1, _2]) + return formatWithArgumentRanges(self._s[2247]!, self._r[2247]!, [_1, _2]) } - public var Checkout_PasswordEntry_Title: String { return self._s[2239]! } - public var PhotoEditor_FadeTool: String { return self._s[2240]! } - public var Privacy_ContactsReset: String { return self._s[2241]! } + public var Checkout_PasswordEntry_Title: String { return self._s[2248]! } + public var PhotoEditor_FadeTool: String { return self._s[2249]! } + public var Privacy_ContactsReset: String { return self._s[2250]! } public func Channel_AdminLog_MessageRestrictedUntil(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2243]!, self._r[2243]!, [_0]) + return formatWithArgumentRanges(self._s[2252]!, self._r[2252]!, [_0]) } - public var Message_PinnedVideoMessage: String { return self._s[2244]! } - public var ChatList_Mute: String { return self._s[2245]! } + public var Message_PinnedVideoMessage: String { return self._s[2253]! } + public var ChatList_Mute: String { return self._s[2254]! } public func Wallet_Time_PreciseDate_m5(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2246]!, self._r[2246]!, [_1, _2, _3]) + return formatWithArgumentRanges(self._s[2255]!, self._r[2255]!, [_1, _2, _3]) } - public var Permissions_CellularDataText_v0: String { return self._s[2247]! } - public var Conversation_PinnedQuiz: String { return self._s[2249]! } - public var ShareMenu_SelectChats: String { return self._s[2251]! } - public var ChatList_Context_Unarchive: String { return self._s[2252]! } - public var MusicPlayer_VoiceNote: String { return self._s[2253]! } - public var Conversation_RestrictedText: String { return self._s[2254]! } - public var SettingsSearch_Synonyms_Privacy_Data_DeleteDrafts: String { return self._s[2255]! } - public var Wallet_Month_GenApril: String { return self._s[2256]! } - public var Wallet_Month_ShortMarch: String { return self._s[2257]! } - public var TwoStepAuth_DisableSuccess: String { return self._s[2258]! } - public var Cache_Videos: String { return self._s[2259]! } - public var PrivacySettings_PhoneNumber: String { return self._s[2260]! } - public var Wallet_Month_GenFebruary: String { return self._s[2261]! } - public var FeatureDisabled_Oops: String { return self._s[2263]! } - public var Passport_Address_PostcodePlaceholder: String { return self._s[2264]! } + public var Permissions_CellularDataText_v0: String { return self._s[2256]! } + public var Conversation_PinnedQuiz: String { return self._s[2258]! } + public var ShareMenu_SelectChats: String { return self._s[2260]! } + public var ChatList_Context_Unarchive: String { return self._s[2261]! } + public var MusicPlayer_VoiceNote: String { return self._s[2262]! } + public var Conversation_RestrictedText: String { return self._s[2263]! } + public var SettingsSearch_Synonyms_Privacy_Data_DeleteDrafts: String { return self._s[2264]! } + public var Wallet_Month_GenApril: String { return self._s[2265]! } + public var Wallet_Month_ShortMarch: String { return self._s[2266]! } + public var TwoStepAuth_DisableSuccess: String { return self._s[2267]! } + public var Cache_Videos: String { return self._s[2268]! } + public var PrivacySettings_PhoneNumber: String { return self._s[2269]! } + public var Wallet_Month_GenFebruary: String { return self._s[2270]! } + public var FeatureDisabled_Oops: String { return self._s[2272]! } + public var Passport_Address_PostcodePlaceholder: String { return self._s[2273]! } public func AddContact_StatusSuccess(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2265]!, self._r[2265]!, [_0]) + return formatWithArgumentRanges(self._s[2274]!, self._r[2274]!, [_0]) } - public var Stickers_GroupStickersHelp: String { return self._s[2267]! } - public var GroupPermission_NoSendPolls: String { return self._s[2268]! } - public var Wallet_Qr_ScanCode: String { return self._s[2269]! } - public var Message_VideoExpired: String { return self._s[2271]! } - public var GroupInfo_GroupHistoryVisible: String { return self._s[2272]! } - public var Notifications_Badge: String { return self._s[2273]! } - public var Wallet_Receive_AddressCopied: String { return self._s[2274]! } - public var CreatePoll_OptionPlaceholder: String { return self._s[2275]! } - public var Username_InvalidTooShort: String { return self._s[2276]! } - public var EnterPasscode_EnterNewPasscodeChange: String { return self._s[2277]! } - public var Channel_AdminLog_PinMessages: String { return self._s[2278]! } - public var ArchivedChats_IntroTitle3: String { return self._s[2279]! } + public var Stickers_GroupStickersHelp: String { return self._s[2276]! } + public var GroupPermission_NoSendPolls: String { return self._s[2277]! } + public var Wallet_Qr_ScanCode: String { return self._s[2278]! } + public var Message_VideoExpired: String { return self._s[2280]! } + public var GroupInfo_GroupHistoryVisible: String { return self._s[2281]! } + public var Notifications_Badge: String { return self._s[2282]! } + public var Wallet_Receive_AddressCopied: String { return self._s[2283]! } + public var CreatePoll_OptionPlaceholder: String { return self._s[2284]! } + public var Username_InvalidTooShort: String { return self._s[2285]! } + public var EnterPasscode_EnterNewPasscodeChange: String { return self._s[2286]! } + public var Channel_AdminLog_PinMessages: String { return self._s[2287]! } + public var ArchivedChats_IntroTitle3: String { return self._s[2288]! } public func Notification_MessageLifetimeRemoved(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2280]!, self._r[2280]!, [_1]) + return formatWithArgumentRanges(self._s[2289]!, self._r[2289]!, [_1]) } - public var Permissions_SiriAllowInSettings_v0: String { return self._s[2281]! } - public var Conversation_DefaultRestrictedText: String { return self._s[2282]! } - public var SharedMedia_CategoryDocs: String { return self._s[2285]! } + public var Permissions_SiriAllowInSettings_v0: String { return self._s[2290]! } + public var Conversation_DefaultRestrictedText: String { return self._s[2291]! } + public var SharedMedia_CategoryDocs: String { return self._s[2294]! } public func PUSH_MESSAGE_CONTACT(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2286]!, self._r[2286]!, [_1]) + return formatWithArgumentRanges(self._s[2295]!, self._r[2295]!, [_1]) } - public var Wallet_Send_UninitializedTitle: String { return self._s[2287]! } - public var StickerPackActionInfo_ArchivedTitle: String { return self._s[2288]! } - public var Privacy_Forwards_NeverLink: String { return self._s[2290]! } + public var Wallet_Send_UninitializedTitle: String { return self._s[2296]! } + public var StickerPackActionInfo_ArchivedTitle: String { return self._s[2297]! } + public var Privacy_Forwards_NeverLink: String { return self._s[2299]! } public func Notification_MessageLifetimeChangedOutgoing(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2291]!, self._r[2291]!, [_1]) + return formatWithArgumentRanges(self._s[2300]!, self._r[2300]!, [_1]) } - public var CheckoutInfo_ErrorShippingNotAvailable: String { return self._s[2292]! } + public var CheckoutInfo_ErrorShippingNotAvailable: String { return self._s[2301]! } public func Time_MonthOfYear_m12(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2293]!, self._r[2293]!, [_0]) + return formatWithArgumentRanges(self._s[2302]!, self._r[2302]!, [_0]) } - public var ChatSettings_PrivateChats: String { return self._s[2294]! } - public var SettingsSearch_Synonyms_EditProfile_Logout: String { return self._s[2295]! } - public var Conversation_PrivateMessageLinkCopied: String { return self._s[2296]! } - public var Channel_UpdatePhotoItem: String { return self._s[2297]! } - public var GroupInfo_LeftStatus: String { return self._s[2298]! } - public var Watch_MessageView_Forward: String { return self._s[2300]! } - public var ReportPeer_ReasonChildAbuse: String { return self._s[2301]! } - public var Cache_ClearEmpty: String { return self._s[2303]! } - public var Localization_LanguageName: String { return self._s[2304]! } - public var Wallet_AccessDenied_Title: String { return self._s[2305]! } - public var WebSearch_GIFs: String { return self._s[2306]! } - public var Notifications_DisplayNamesOnLockScreenInfoWithLink: String { return self._s[2307]! } - public var Wallet_AccessDenied_Settings: String { return self._s[2308]! } - public var Username_InvalidStartsWithNumber: String { return self._s[2309]! } - public var Common_Back: String { return self._s[2310]! } - public var GroupInfo_Permissions_EditingDisabled: String { return self._s[2311]! } - public var Passport_Identity_DateOfBirthPlaceholder: String { return self._s[2312]! } - public var Wallet_Send_Send: String { return self._s[2313]! } + public var ChatSettings_PrivateChats: String { return self._s[2303]! } + public var SettingsSearch_Synonyms_EditProfile_Logout: String { return self._s[2304]! } + public var Conversation_PrivateMessageLinkCopied: String { return self._s[2305]! } + public var Channel_UpdatePhotoItem: String { return self._s[2306]! } + public var GroupInfo_LeftStatus: String { return self._s[2307]! } + public var Watch_MessageView_Forward: String { return self._s[2309]! } + public var ReportPeer_ReasonChildAbuse: String { return self._s[2310]! } + public var Cache_ClearEmpty: String { return self._s[2312]! } + public var Localization_LanguageName: String { return self._s[2313]! } + public var Wallet_AccessDenied_Title: String { return self._s[2314]! } + public var WebSearch_GIFs: String { return self._s[2315]! } + public var Notifications_DisplayNamesOnLockScreenInfoWithLink: String { return self._s[2316]! } + public var Wallet_AccessDenied_Settings: String { return self._s[2317]! } + public var Username_InvalidStartsWithNumber: String { return self._s[2318]! } + public var Common_Back: String { return self._s[2319]! } + public var GroupInfo_Permissions_EditingDisabled: String { return self._s[2320]! } + public var Passport_Identity_DateOfBirthPlaceholder: String { return self._s[2321]! } + public var Wallet_Send_Send: String { return self._s[2322]! } public func PUSH_CHANNEL_MESSAGE_STICKER(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2315]!, self._r[2315]!, [_1, _2]) + return formatWithArgumentRanges(self._s[2324]!, self._r[2324]!, [_1, _2]) } - public var Wallet_Info_RefreshErrorTitle: String { return self._s[2316]! } - public var Wallet_Month_GenJune: String { return self._s[2317]! } - public var Passport_Email_Help: String { return self._s[2318]! } - public var Watch_Conversation_Reply: String { return self._s[2320]! } - public var Conversation_EditingMessageMediaChange: String { return self._s[2323]! } - public var Passport_Identity_IssueDatePlaceholder: String { return self._s[2324]! } - public var Channel_BanUser_Unban: String { return self._s[2326]! } - public var Channel_EditAdmin_PermissionPostMessages: String { return self._s[2327]! } - public var Group_Username_CreatePublicLinkHelp: String { return self._s[2328]! } - public var TwoStepAuth_ConfirmEmailCodePlaceholder: String { return self._s[2330]! } - public var Wallet_Send_AddressHeader: String { return self._s[2331]! } - public var Passport_Identity_Name: String { return self._s[2332]! } + public var Wallet_Info_RefreshErrorTitle: String { return self._s[2325]! } + public var Wallet_Month_GenJune: String { return self._s[2326]! } + public var Passport_Email_Help: String { return self._s[2327]! } + public var Watch_Conversation_Reply: String { return self._s[2329]! } + public var Conversation_EditingMessageMediaChange: String { return self._s[2332]! } + public var Passport_Identity_IssueDatePlaceholder: String { return self._s[2333]! } + public var Channel_BanUser_Unban: String { return self._s[2335]! } + public var Channel_EditAdmin_PermissionPostMessages: String { return self._s[2336]! } + public var Group_Username_CreatePublicLinkHelp: String { return self._s[2337]! } + public var TwoStepAuth_ConfirmEmailCodePlaceholder: String { return self._s[2339]! } + public var Wallet_Send_AddressHeader: String { return self._s[2340]! } + public var Passport_Identity_Name: String { return self._s[2341]! } public func Channel_DiscussionGroup_HeaderGroupSet(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2333]!, self._r[2333]!, [_0]) + return formatWithArgumentRanges(self._s[2342]!, self._r[2342]!, [_0]) } - public var GroupRemoved_ViewUserInfo: String { return self._s[2334]! } - public var Conversation_BlockUser: String { return self._s[2335]! } - public var Month_GenJanuary: String { return self._s[2336]! } - public var ChatSettings_TextSize: String { return self._s[2337]! } - public var Notification_PassportValuePhone: String { return self._s[2338]! } - public var MediaPlayer_UnknownArtist: String { return self._s[2339]! } - public var Passport_Language_ne: String { return self._s[2340]! } - public var Notification_CallBack: String { return self._s[2341]! } - public var Wallet_SecureStorageReset_BiometryTouchId: String { return self._s[2342]! } - public var TwoStepAuth_EmailHelp: String { return self._s[2343]! } + public var GroupRemoved_ViewUserInfo: String { return self._s[2343]! } + public var Conversation_BlockUser: String { return self._s[2344]! } + public var Month_GenJanuary: String { return self._s[2345]! } + public var ChatSettings_TextSize: String { return self._s[2346]! } + public var Notification_PassportValuePhone: String { return self._s[2347]! } + public var MediaPlayer_UnknownArtist: String { return self._s[2348]! } + public var Passport_Language_ne: String { return self._s[2349]! } + public var Notification_CallBack: String { return self._s[2350]! } + public var Wallet_SecureStorageReset_BiometryTouchId: String { return self._s[2351]! } + public var TwoStepAuth_EmailHelp: String { return self._s[2352]! } public func Time_YesterdayAt(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2344]!, self._r[2344]!, [_0]) + return formatWithArgumentRanges(self._s[2353]!, self._r[2353]!, [_0]) } - public var Channel_Info_Management: String { return self._s[2345]! } - public var Passport_FieldIdentityUploadHelp: String { return self._s[2346]! } - public var Stickers_FrequentlyUsed: String { return self._s[2347]! } - public var Channel_BanUser_PermissionSendMessages: String { return self._s[2348]! } - public var Passport_Address_OneOfTypeUtilityBill: String { return self._s[2350]! } + public var Channel_Info_Management: String { return self._s[2354]! } + public var Passport_FieldIdentityUploadHelp: String { return self._s[2355]! } + public var Stickers_FrequentlyUsed: String { return self._s[2356]! } + public var Channel_BanUser_PermissionSendMessages: String { return self._s[2357]! } + public var Passport_Address_OneOfTypeUtilityBill: String { return self._s[2359]! } public func LOCAL_CHANNEL_MESSAGE_FWDS(_ _1: String, _ _2: Int) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2351]!, self._r[2351]!, [_1, "\(_2)"]) + return formatWithArgumentRanges(self._s[2360]!, self._r[2360]!, [_1, "\(_2)"]) } - public var TwoFactorSetup_Password_Title: String { return self._s[2352]! } - public var Passport_Address_EditResidentialAddress: String { return self._s[2353]! } - public var PrivacyPolicy_DeclineTitle: String { return self._s[2354]! } - public var CreatePoll_TextHeader: String { return self._s[2355]! } + public var TwoFactorSetup_Password_Title: String { return self._s[2361]! } + public var Passport_Address_EditResidentialAddress: String { return self._s[2362]! } + public var PrivacyPolicy_DeclineTitle: String { return self._s[2363]! } + public var CreatePoll_TextHeader: String { return self._s[2364]! } public func Checkout_SavePasswordTimeoutAndTouchId(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2356]!, self._r[2356]!, [_0]) + return formatWithArgumentRanges(self._s[2365]!, self._r[2365]!, [_0]) } - public var PhotoEditor_QualityMedium: String { return self._s[2357]! } - public var InfoPlist_NSMicrophoneUsageDescription: String { return self._s[2358]! } - public var Conversation_StatusKickedFromChannel: String { return self._s[2360]! } - public var CheckoutInfo_ReceiverInfoName: String { return self._s[2361]! } - public var Group_ErrorSendRestrictedStickers: String { return self._s[2362]! } + public var PhotoEditor_QualityMedium: String { return self._s[2366]! } + public var InfoPlist_NSMicrophoneUsageDescription: String { return self._s[2367]! } + public var Conversation_StatusKickedFromChannel: String { return self._s[2369]! } + public var CheckoutInfo_ReceiverInfoName: String { return self._s[2370]! } + public var Group_ErrorSendRestrictedStickers: String { return self._s[2371]! } public func Conversation_RestrictedInlineTimed(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2363]!, self._r[2363]!, [_0]) + return formatWithArgumentRanges(self._s[2372]!, self._r[2372]!, [_0]) } public func Channel_AdminLog_MessageTransferedName(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2364]!, self._r[2364]!, [_1]) + return formatWithArgumentRanges(self._s[2373]!, self._r[2373]!, [_1]) } - public var LogoutOptions_LogOutWalletInfo: String { return self._s[2365]! } - public var TwoFactorSetup_Email_SkipConfirmationTitle: String { return self._s[2366]! } - public var Conversation_LinkDialogOpen: String { return self._s[2368]! } - public var TwoFactorSetup_Hint_Title: String { return self._s[2369]! } - public var VoiceOver_Chat_PollNoVotes: String { return self._s[2370]! } - public var Settings_Username: String { return self._s[2372]! } - public var Conversation_Block: String { return self._s[2374]! } - public var Wallpaper_Wallpaper: String { return self._s[2375]! } - public var SocksProxySetup_UseProxy: String { return self._s[2377]! } - public var Wallet_Send_Confirmation: String { return self._s[2378]! } - public var EditTheme_UploadEditedTheme: String { return self._s[2379]! } - public var UserInfo_ShareMyContactInfo: String { return self._s[2380]! } - public var MessageTimer_Forever: String { return self._s[2381]! } - public var Privacy_Calls_WhoCanCallMe: String { return self._s[2382]! } - public var PhotoEditor_DiscardChanges: String { return self._s[2383]! } - public var AuthSessions_TerminateOtherSessionsHelp: String { return self._s[2384]! } - public var Passport_Language_da: String { return self._s[2385]! } - public var SocksProxySetup_PortPlaceholder: String { return self._s[2386]! } + public var LogoutOptions_LogOutWalletInfo: String { return self._s[2374]! } + public var TwoFactorSetup_Email_SkipConfirmationTitle: String { return self._s[2375]! } + public var Conversation_LinkDialogOpen: String { return self._s[2377]! } + public var TwoFactorSetup_Hint_Title: String { return self._s[2378]! } + public var VoiceOver_Chat_PollNoVotes: String { return self._s[2379]! } + public var Settings_Username: String { return self._s[2381]! } + public var Conversation_Block: String { return self._s[2383]! } + public var Wallpaper_Wallpaper: String { return self._s[2384]! } + public var SocksProxySetup_UseProxy: String { return self._s[2386]! } + public var Wallet_Send_Confirmation: String { return self._s[2387]! } + public var EditTheme_UploadEditedTheme: String { return self._s[2388]! } + public var UserInfo_ShareMyContactInfo: String { return self._s[2389]! } + public var MessageTimer_Forever: String { return self._s[2390]! } + public var Privacy_Calls_WhoCanCallMe: String { return self._s[2391]! } + public var PhotoEditor_DiscardChanges: String { return self._s[2392]! } + public var AuthSessions_TerminateOtherSessionsHelp: String { return self._s[2393]! } + public var Passport_Language_da: String { return self._s[2394]! } + public var SocksProxySetup_PortPlaceholder: String { return self._s[2395]! } public func SecretGIF_NotViewedYet(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2387]!, self._r[2387]!, [_0]) + return formatWithArgumentRanges(self._s[2396]!, self._r[2396]!, [_0]) } - public var Passport_Address_EditPassportRegistration: String { return self._s[2388]! } + public var Passport_Address_EditPassportRegistration: String { return self._s[2397]! } public func Channel_AdminLog_MessageChangedGroupAbout(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2390]!, self._r[2390]!, [_0]) + return formatWithArgumentRanges(self._s[2399]!, self._r[2399]!, [_0]) } - public var Settings_AddDevice: String { return self._s[2391]! } - public var Passport_Identity_ResidenceCountryPlaceholder: String { return self._s[2393]! } - public var AuthSessions_AddDeviceIntro_Text1: String { return self._s[2394]! } - public var Conversation_SearchByName_Prefix: String { return self._s[2395]! } - public var Conversation_PinnedPoll: String { return self._s[2396]! } - public var AuthSessions_AddDeviceIntro_Text2: String { return self._s[2397]! } - public var Conversation_EmptyGifPanelPlaceholder: String { return self._s[2398]! } - public var AuthSessions_AddDeviceIntro_Text3: String { return self._s[2399]! } + public var Settings_AddDevice: String { return self._s[2400]! } + public var Passport_Identity_ResidenceCountryPlaceholder: String { return self._s[2402]! } + public var AuthSessions_AddDeviceIntro_Text1: String { return self._s[2403]! } + public var Conversation_SearchByName_Prefix: String { return self._s[2404]! } + public var Conversation_PinnedPoll: String { return self._s[2405]! } + public var AuthSessions_AddDeviceIntro_Text2: String { return self._s[2406]! } + public var Conversation_EmptyGifPanelPlaceholder: String { return self._s[2407]! } + public var AuthSessions_AddDeviceIntro_Text3: String { return self._s[2408]! } public func PUSH_ENCRYPTION_ACCEPT(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2400]!, self._r[2400]!, [_1]) + return formatWithArgumentRanges(self._s[2409]!, self._r[2409]!, [_1]) } - public var WallpaperSearch_ColorPurple: String { return self._s[2401]! } - public var Cache_ByPeerHeader: String { return self._s[2402]! } + public var WallpaperSearch_ColorPurple: String { return self._s[2410]! } + public var Cache_ByPeerHeader: String { return self._s[2411]! } public func Conversation_EncryptedPlaceholderTitleIncoming(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2403]!, self._r[2403]!, [_0]) + return formatWithArgumentRanges(self._s[2412]!, self._r[2412]!, [_0]) } - public var ChatSettings_AutoDownloadDocuments: String { return self._s[2404]! } - public var Appearance_ThemePreview_Chat_3_Text: String { return self._s[2407]! } - public var Wallet_Completed_Title: String { return self._s[2408]! } - public var Notification_PinnedMessage: String { return self._s[2409]! } - public var TwoFactorSetup_EmailVerification_Placeholder: String { return self._s[2410]! } - public var VoiceOver_Chat_RecordModeVideoMessage: String { return self._s[2412]! } - public var Contacts_SortBy: String { return self._s[2413]! } + public var ChatSettings_AutoDownloadDocuments: String { return self._s[2413]! } + public var Appearance_ThemePreview_Chat_3_Text: String { return self._s[2416]! } + public var Wallet_Completed_Title: String { return self._s[2417]! } + public var Notification_PinnedMessage: String { return self._s[2418]! } + public var TwoFactorSetup_EmailVerification_Placeholder: String { return self._s[2419]! } + public var VoiceOver_Chat_RecordModeVideoMessage: String { return self._s[2421]! } + public var Contacts_SortBy: String { return self._s[2422]! } public func PUSH_CHANNEL_MESSAGE_NOTEXT(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2414]!, self._r[2414]!, [_1]) + return formatWithArgumentRanges(self._s[2423]!, self._r[2423]!, [_1]) } - public var Appearance_ColorThemeNight: String { return self._s[2416]! } + public var Appearance_ColorThemeNight: String { return self._s[2425]! } public func PUSH_MESSAGE_GAME(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2417]!, self._r[2417]!, [_1, _2]) + return formatWithArgumentRanges(self._s[2426]!, self._r[2426]!, [_1, _2]) } - public var Call_EncryptionKey_Title: String { return self._s[2418]! } - public var Watch_UserInfo_Service: String { return self._s[2419]! } - public var SettingsSearch_Synonyms_Data_SaveEditedPhotos: String { return self._s[2421]! } - public var Conversation_Unpin: String { return self._s[2423]! } - public var CancelResetAccount_Title: String { return self._s[2424]! } - public var Map_LiveLocationFor15Minutes: String { return self._s[2425]! } + public var Call_EncryptionKey_Title: String { return self._s[2427]! } + public var Watch_UserInfo_Service: String { return self._s[2428]! } + public var SettingsSearch_Synonyms_Data_SaveEditedPhotos: String { return self._s[2430]! } + public var Conversation_Unpin: String { return self._s[2432]! } + public var CancelResetAccount_Title: String { return self._s[2433]! } + public var Map_LiveLocationFor15Minutes: String { return self._s[2434]! } public func Time_PreciseDate_m8(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2427]!, self._r[2427]!, [_1, _2, _3]) + return formatWithArgumentRanges(self._s[2436]!, self._r[2436]!, [_1, _2, _3]) } - public var Group_Members_AddMemberBotErrorNotAllowed: String { return self._s[2428]! } - public var Appearance_BubbleCorners_Title: String { return self._s[2429]! } - public var CallSettings_Title: String { return self._s[2430]! } - public var SettingsSearch_Synonyms_Appearance_ChatBackground: String { return self._s[2431]! } - public var PasscodeSettings_EncryptDataHelp: String { return self._s[2433]! } - public var AutoDownloadSettings_Contacts: String { return self._s[2434]! } + public var Group_Members_AddMemberBotErrorNotAllowed: String { return self._s[2437]! } + public var Appearance_BubbleCorners_Title: String { return self._s[2438]! } + public var CallSettings_Title: String { return self._s[2439]! } + public var SettingsSearch_Synonyms_Appearance_ChatBackground: String { return self._s[2440]! } + public var PasscodeSettings_EncryptDataHelp: String { return self._s[2442]! } + public var AutoDownloadSettings_Contacts: String { return self._s[2443]! } public func Channel_AdminLog_MessageRankName(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2435]!, self._r[2435]!, [_1, _2]) + return formatWithArgumentRanges(self._s[2444]!, self._r[2444]!, [_1, _2]) } - public var Passport_Identity_DocumentDetails: String { return self._s[2436]! } - public var LoginPassword_PasswordHelp: String { return self._s[2437]! } - public var SettingsSearch_Synonyms_Data_AutoDownloadUsingWifi: String { return self._s[2438]! } - public var PrivacyLastSeenSettings_CustomShareSettings_Delete: String { return self._s[2439]! } - public var ChatContextMenu_TextSelectionTip: String { return self._s[2440]! } - public var Checkout_TotalPaidAmount: String { return self._s[2441]! } + public var Passport_Identity_DocumentDetails: String { return self._s[2445]! } + public var LoginPassword_PasswordHelp: String { return self._s[2446]! } + public var SettingsSearch_Synonyms_Data_AutoDownloadUsingWifi: String { return self._s[2447]! } + public var PrivacyLastSeenSettings_CustomShareSettings_Delete: String { return self._s[2448]! } + public var ChatContextMenu_TextSelectionTip: String { return self._s[2449]! } + public var Checkout_TotalPaidAmount: String { return self._s[2450]! } public func FileSize_KB(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2442]!, self._r[2442]!, [_0]) + return formatWithArgumentRanges(self._s[2451]!, self._r[2451]!, [_0]) } - public var ChatState_Updating: String { return self._s[2443]! } - public var PasscodeSettings_ChangePasscode: String { return self._s[2444]! } - public var Conversation_SecretLinkPreviewAlert: String { return self._s[2446]! } - public var Privacy_SecretChatsLinkPreviews: String { return self._s[2447]! } + public var ChatState_Updating: String { return self._s[2452]! } + public var PasscodeSettings_ChangePasscode: String { return self._s[2453]! } + public var Conversation_SecretLinkPreviewAlert: String { return self._s[2455]! } + public var Privacy_SecretChatsLinkPreviews: String { return self._s[2456]! } public func PUSH_CHANNEL_MESSAGE_DOC(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2448]!, self._r[2448]!, [_1]) + return formatWithArgumentRanges(self._s[2457]!, self._r[2457]!, [_1]) } - public var VoiceOver_Chat_ReplyToYourMessage: String { return self._s[2449]! } - public var Contacts_InviteFriends: String { return self._s[2451]! } - public var Map_ChooseLocationTitle: String { return self._s[2452]! } - public var Conversation_StopPoll: String { return self._s[2454]! } + public var VoiceOver_Chat_ReplyToYourMessage: String { return self._s[2458]! } + public var Contacts_InviteFriends: String { return self._s[2460]! } + public var Map_ChooseLocationTitle: String { return self._s[2461]! } + public var Conversation_StopPoll: String { return self._s[2463]! } public func WebSearch_SearchNoResultsDescription(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2455]!, self._r[2455]!, [_0]) + return formatWithArgumentRanges(self._s[2464]!, self._r[2464]!, [_0]) } - public var Call_Camera: String { return self._s[2456]! } - public var LogoutOptions_ChangePhoneNumberTitle: String { return self._s[2457]! } - public var AppWallet_Intro_Text: String { return self._s[2458]! } - public var Appearance_BubbleCornersSetting: String { return self._s[2459]! } - public var Calls_RatingFeedback: String { return self._s[2460]! } - public var GroupInfo_BroadcastListNamePlaceholder: String { return self._s[2462]! } - public var Wallet_Alert_OK: String { return self._s[2463]! } - public var NotificationsSound_Pulse: String { return self._s[2464]! } - public var Watch_LastSeen_Lately: String { return self._s[2465]! } - public var ReportGroupLocation_Report: String { return self._s[2468]! } - public var Widget_NoUsers: String { return self._s[2469]! } - public var Conversation_UnvotePoll: String { return self._s[2470]! } - public var SettingsSearch_Synonyms_Privacy_ProfilePhoto: String { return self._s[2472]! } - public var Privacy_ProfilePhoto_WhoCanSeeMyPhoto: String { return self._s[2473]! } - public var NotificationsSound_Circles: String { return self._s[2474]! } - public var PrivacyLastSeenSettings_AlwaysShareWith_Title: String { return self._s[2477]! } - public var Wallet_Settings_DeleteWallet: String { return self._s[2478]! } - public var TwoStepAuth_RecoveryCodeExpired: String { return self._s[2479]! } - public var Proxy_TooltipUnavailable: String { return self._s[2480]! } - public var Passport_Identity_CountryPlaceholder: String { return self._s[2482]! } - public var GroupInfo_Permissions_SlowmodeInfo: String { return self._s[2484]! } - public var Conversation_FileDropbox: String { return self._s[2485]! } - public var Notifications_ExceptionsUnmuted: String { return self._s[2486]! } - public var Tour_Text3: String { return self._s[2488]! } - public var Login_ResetAccountProtected_Title: String { return self._s[2490]! } - public var GroupPermission_NoSendMessages: String { return self._s[2491]! } - public var WallpaperSearch_ColorTitle: String { return self._s[2492]! } - public var ChatAdmins_AllMembersAreAdminsOnHelp: String { return self._s[2493]! } + public var Call_Camera: String { return self._s[2465]! } + public var LogoutOptions_ChangePhoneNumberTitle: String { return self._s[2466]! } + public var AppWallet_Intro_Text: String { return self._s[2467]! } + public var Appearance_BubbleCornersSetting: String { return self._s[2468]! } + public var Calls_RatingFeedback: String { return self._s[2469]! } + public var GroupInfo_BroadcastListNamePlaceholder: String { return self._s[2471]! } + public var Wallet_Alert_OK: String { return self._s[2472]! } + public var NotificationsSound_Pulse: String { return self._s[2473]! } + public var Watch_LastSeen_Lately: String { return self._s[2474]! } + public var ReportGroupLocation_Report: String { return self._s[2477]! } + public var Widget_NoUsers: String { return self._s[2478]! } + public var Conversation_UnvotePoll: String { return self._s[2479]! } + public var SettingsSearch_Synonyms_Privacy_ProfilePhoto: String { return self._s[2481]! } + public var Privacy_ProfilePhoto_WhoCanSeeMyPhoto: String { return self._s[2482]! } + public var NotificationsSound_Circles: String { return self._s[2483]! } + public var PrivacyLastSeenSettings_AlwaysShareWith_Title: String { return self._s[2486]! } + public var Wallet_Settings_DeleteWallet: String { return self._s[2487]! } + public var TwoStepAuth_RecoveryCodeExpired: String { return self._s[2488]! } + public var Proxy_TooltipUnavailable: String { return self._s[2489]! } + public var Passport_Identity_CountryPlaceholder: String { return self._s[2491]! } + public var GroupInfo_Permissions_SlowmodeInfo: String { return self._s[2493]! } + public var Conversation_FileDropbox: String { return self._s[2494]! } + public var Notifications_ExceptionsUnmuted: String { return self._s[2495]! } + public var Tour_Text3: String { return self._s[2497]! } + public var Login_ResetAccountProtected_Title: String { return self._s[2499]! } + public var GroupPermission_NoSendMessages: String { return self._s[2500]! } + public var WallpaperSearch_ColorTitle: String { return self._s[2501]! } + public var ChatAdmins_AllMembersAreAdminsOnHelp: String { return self._s[2502]! } public func Conversation_LiveLocationYouAnd(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2495]!, self._r[2495]!, [_0]) + return formatWithArgumentRanges(self._s[2504]!, self._r[2504]!, [_0]) } - public var GroupInfo_AddParticipantTitle: String { return self._s[2496]! } - public var Checkout_ShippingOption_Title: String { return self._s[2497]! } - public var ChatSettings_AutoDownloadTitle: String { return self._s[2498]! } + public var GroupInfo_AddParticipantTitle: String { return self._s[2505]! } + public var Checkout_ShippingOption_Title: String { return self._s[2506]! } + public var ChatSettings_AutoDownloadTitle: String { return self._s[2507]! } public func DialogList_SingleTypingSuffix(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2499]!, self._r[2499]!, [_0]) + return formatWithArgumentRanges(self._s[2508]!, self._r[2508]!, [_0]) } public func ChatSettings_AutoDownloadSettings_TypeVideo(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2500]!, self._r[2500]!, [_0]) + return formatWithArgumentRanges(self._s[2509]!, self._r[2509]!, [_0]) } - public var Channel_Management_LabelAdministrator: String { return self._s[2501]! } - public var EditTheme_FileReadError: String { return self._s[2502]! } - public var OwnershipTransfer_ComeBackLater: String { return self._s[2503]! } - public var PrivacyLastSeenSettings_NeverShareWith_Placeholder: String { return self._s[2504]! } - public var AutoDownloadSettings_Photos: String { return self._s[2506]! } - public var Appearance_PreviewIncomingText: String { return self._s[2507]! } - public var ChatList_Context_MarkAllAsRead: String { return self._s[2508]! } - public var ChannelInfo_ConfirmLeave: String { return self._s[2509]! } - public var MediaPicker_MomentsDateRangeSameMonthYearFormat: String { return self._s[2510]! } - public var Passport_Identity_DocumentNumberPlaceholder: String { return self._s[2511]! } - public var Channel_AdminLogFilter_EventsNewMembers: String { return self._s[2512]! } - public var PasscodeSettings_AutoLock_IfAwayFor_5minutes: String { return self._s[2513]! } - public var GroupInfo_SetGroupPhotoStop: String { return self._s[2514]! } - public var Notification_SecretChatScreenshot: String { return self._s[2515]! } - public var AccessDenied_Wallpapers: String { return self._s[2516]! } - public var ChatList_Context_Mute: String { return self._s[2518]! } - public var Passport_Address_City: String { return self._s[2519]! } - public var InfoPlist_NSPhotoLibraryAddUsageDescription: String { return self._s[2520]! } - public var Appearance_ThemeCarouselClassic: String { return self._s[2521]! } - public var SocksProxySetup_SecretPlaceholder: String { return self._s[2522]! } - public var AccessDenied_LocationDisabled: String { return self._s[2523]! } - public var Group_Location_Title: String { return self._s[2524]! } - public var SocksProxySetup_HostnamePlaceholder: String { return self._s[2526]! } - public var GroupInfo_Sound: String { return self._s[2527]! } - public var SettingsSearch_Synonyms_ChatSettings_OpenLinksIn: String { return self._s[2528]! } - public var ChannelInfo_ScamChannelWarning: String { return self._s[2529]! } - public var Stickers_RemoveFromFavorites: String { return self._s[2530]! } - public var Contacts_Title: String { return self._s[2531]! } - public var EditTheme_ThemeTemplateAlertText: String { return self._s[2532]! } - public var Passport_Language_fr: String { return self._s[2533]! } - public var TwoFactorSetup_EmailVerification_Action: String { return self._s[2534]! } - public var Notifications_ResetAllNotifications: String { return self._s[2535]! } - public var IntentsSettings_SuggestedChats: String { return self._s[2537]! } - public var PrivacySettings_SecurityTitle: String { return self._s[2539]! } - public var Checkout_NewCard_Title: String { return self._s[2540]! } - public var Login_HaveNotReceivedCodeInternal: String { return self._s[2541]! } - public var Conversation_ForwardChats: String { return self._s[2542]! } - public var Wallet_SecureStorageReset_PasscodeText: String { return self._s[2544]! } - public var PasscodeSettings_4DigitCode: String { return self._s[2545]! } - public var Settings_FAQ: String { return self._s[2547]! } - public var AutoDownloadSettings_DocumentsTitle: String { return self._s[2548]! } - public var Conversation_ContextMenuForward: String { return self._s[2549]! } - public var VoiceOver_Chat_YourPhoto: String { return self._s[2552]! } - public var PrivacyPolicy_Title: String { return self._s[2555]! } - public var Notifications_TextTone: String { return self._s[2556]! } - public var Profile_CreateNewContact: String { return self._s[2557]! } - public var PrivacyPhoneNumberSettings_WhoCanSeeMyPhoneNumber: String { return self._s[2558]! } - public var TwoFactorSetup_EmailVerification_Title: String { return self._s[2560]! } - public var Call_Speaker: String { return self._s[2561]! } - public var AutoNightTheme_AutomaticSection: String { return self._s[2562]! } - public var Channel_OwnershipTransfer_EnterPassword: String { return self._s[2564]! } - public var Channel_Username_InvalidCharacters: String { return self._s[2565]! } + public var Channel_Management_LabelAdministrator: String { return self._s[2510]! } + public var EditTheme_FileReadError: String { return self._s[2511]! } + public var OwnershipTransfer_ComeBackLater: String { return self._s[2512]! } + public var PrivacyLastSeenSettings_NeverShareWith_Placeholder: String { return self._s[2513]! } + public var AutoDownloadSettings_Photos: String { return self._s[2515]! } + public var Appearance_PreviewIncomingText: String { return self._s[2516]! } + public var ChatList_Context_MarkAllAsRead: String { return self._s[2517]! } + public var ChannelInfo_ConfirmLeave: String { return self._s[2518]! } + public var MediaPicker_MomentsDateRangeSameMonthYearFormat: String { return self._s[2519]! } + public var Passport_Identity_DocumentNumberPlaceholder: String { return self._s[2520]! } + public var Channel_AdminLogFilter_EventsNewMembers: String { return self._s[2521]! } + public var PasscodeSettings_AutoLock_IfAwayFor_5minutes: String { return self._s[2522]! } + public var GroupInfo_SetGroupPhotoStop: String { return self._s[2523]! } + public var Notification_SecretChatScreenshot: String { return self._s[2524]! } + public var AccessDenied_Wallpapers: String { return self._s[2525]! } + public var ChatList_Context_Mute: String { return self._s[2527]! } + public var Passport_Address_City: String { return self._s[2528]! } + public var InfoPlist_NSPhotoLibraryAddUsageDescription: String { return self._s[2529]! } + public var Appearance_ThemeCarouselClassic: String { return self._s[2530]! } + public var SocksProxySetup_SecretPlaceholder: String { return self._s[2531]! } + public var AccessDenied_LocationDisabled: String { return self._s[2532]! } + public var Group_Location_Title: String { return self._s[2533]! } + public var SocksProxySetup_HostnamePlaceholder: String { return self._s[2535]! } + public var GroupInfo_Sound: String { return self._s[2536]! } + public var SettingsSearch_Synonyms_ChatSettings_OpenLinksIn: String { return self._s[2537]! } + public var ChannelInfo_ScamChannelWarning: String { return self._s[2538]! } + public var Stickers_RemoveFromFavorites: String { return self._s[2539]! } + public var Contacts_Title: String { return self._s[2540]! } + public var EditTheme_ThemeTemplateAlertText: String { return self._s[2541]! } + public var Passport_Language_fr: String { return self._s[2542]! } + public var TwoFactorSetup_EmailVerification_Action: String { return self._s[2543]! } + public var Notifications_ResetAllNotifications: String { return self._s[2544]! } + public var IntentsSettings_SuggestedChats: String { return self._s[2546]! } + public var PrivacySettings_SecurityTitle: String { return self._s[2548]! } + public var Checkout_NewCard_Title: String { return self._s[2549]! } + public var Login_HaveNotReceivedCodeInternal: String { return self._s[2550]! } + public var Conversation_ForwardChats: String { return self._s[2551]! } + public var Wallet_SecureStorageReset_PasscodeText: String { return self._s[2553]! } + public var PasscodeSettings_4DigitCode: String { return self._s[2554]! } + public var Settings_FAQ: String { return self._s[2556]! } + public var AutoDownloadSettings_DocumentsTitle: String { return self._s[2557]! } + public var Conversation_ContextMenuForward: String { return self._s[2558]! } + public var VoiceOver_Chat_YourPhoto: String { return self._s[2561]! } + public var PrivacyPolicy_Title: String { return self._s[2564]! } + public var Notifications_TextTone: String { return self._s[2565]! } + public var Profile_CreateNewContact: String { return self._s[2566]! } + public var PrivacyPhoneNumberSettings_WhoCanSeeMyPhoneNumber: String { return self._s[2567]! } + public var TwoFactorSetup_EmailVerification_Title: String { return self._s[2569]! } + public var Call_Speaker: String { return self._s[2570]! } + public var AutoNightTheme_AutomaticSection: String { return self._s[2571]! } + public var Channel_OwnershipTransfer_EnterPassword: String { return self._s[2573]! } + public var Channel_Username_InvalidCharacters: String { return self._s[2574]! } public func Channel_AdminLog_MessageChangedChannelUsername(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2566]!, self._r[2566]!, [_0]) + return formatWithArgumentRanges(self._s[2575]!, self._r[2575]!, [_0]) } - public var AutoDownloadSettings_AutodownloadFiles: String { return self._s[2567]! } - public var PrivacySettings_LastSeenTitle: String { return self._s[2568]! } - public var Channel_AdminLog_CanInviteUsers: String { return self._s[2569]! } - public var SettingsSearch_Synonyms_Privacy_Data_ClearPaymentsInfo: String { return self._s[2570]! } - public var OwnershipTransfer_SecurityCheck: String { return self._s[2571]! } - public var Conversation_MessageDeliveryFailed: String { return self._s[2572]! } - public var Watch_ChatList_NoConversationsText: String { return self._s[2573]! } - public var Bot_Unblock: String { return self._s[2574]! } - public var TextFormat_Italic: String { return self._s[2575]! } - public var WallpaperSearch_ColorPink: String { return self._s[2576]! } - public var Settings_About_Help: String { return self._s[2578]! } - public var SearchImages_Title: String { return self._s[2579]! } - public var Weekday_Wednesday: String { return self._s[2580]! } - public var Conversation_ClousStorageInfo_Description1: String { return self._s[2581]! } - public var ExplicitContent_AlertTitle: String { return self._s[2582]! } + public var AutoDownloadSettings_AutodownloadFiles: String { return self._s[2576]! } + public var PrivacySettings_LastSeenTitle: String { return self._s[2577]! } + public var Channel_AdminLog_CanInviteUsers: String { return self._s[2578]! } + public var SettingsSearch_Synonyms_Privacy_Data_ClearPaymentsInfo: String { return self._s[2579]! } + public var OwnershipTransfer_SecurityCheck: String { return self._s[2580]! } + public var Conversation_MessageDeliveryFailed: String { return self._s[2581]! } + public var Watch_ChatList_NoConversationsText: String { return self._s[2582]! } + public var Bot_Unblock: String { return self._s[2583]! } + public var TextFormat_Italic: String { return self._s[2584]! } + public var WallpaperSearch_ColorPink: String { return self._s[2585]! } + public var Settings_About_Help: String { return self._s[2587]! } + public var SearchImages_Title: String { return self._s[2588]! } + public var Weekday_Wednesday: String { return self._s[2589]! } + public var Conversation_ClousStorageInfo_Description1: String { return self._s[2590]! } + public var ExplicitContent_AlertTitle: String { return self._s[2591]! } public func Time_PreciseDate_m5(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2583]!, self._r[2583]!, [_1, _2, _3]) + return formatWithArgumentRanges(self._s[2592]!, self._r[2592]!, [_1, _2, _3]) } - public var Channel_DiscussionGroup_Create: String { return self._s[2584]! } - public var Weekday_Thursday: String { return self._s[2585]! } - public var Channel_BanUser_PermissionChangeGroupInfo: String { return self._s[2586]! } - public var Channel_Members_AddMembersHelp: String { return self._s[2587]! } + public var Channel_DiscussionGroup_Create: String { return self._s[2593]! } + public var Weekday_Thursday: String { return self._s[2594]! } + public var Channel_BanUser_PermissionChangeGroupInfo: String { return self._s[2595]! } + public var Channel_Members_AddMembersHelp: String { return self._s[2596]! } public func Checkout_SavePasswordTimeout(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2588]!, self._r[2588]!, [_0]) + return formatWithArgumentRanges(self._s[2597]!, self._r[2597]!, [_0]) } - public var Channel_DiscussionGroup_LinkGroup: String { return self._s[2589]! } - public var SettingsSearch_Synonyms_Notifications_InAppNotificationsVibrate: String { return self._s[2590]! } - public var Passport_RequestedInformation: String { return self._s[2591]! } - public var Login_PhoneAndCountryHelp: String { return self._s[2592]! } - public var Conversation_EncryptionProcessing: String { return self._s[2594]! } - public var Notifications_PermissionsSuppressWarningTitle: String { return self._s[2595]! } - public var PhotoEditor_EnhanceTool: String { return self._s[2597]! } - public var Channel_Setup_Title: String { return self._s[2598]! } - public var Conversation_SearchPlaceholder: String { return self._s[2599]! } - public var OldChannels_GroupEmptyFormat: String { return self._s[2600]! } - public var AccessDenied_LocationAlwaysDenied: String { return self._s[2601]! } - public var Checkout_ErrorGeneric: String { return self._s[2602]! } - public var Passport_Language_hu: String { return self._s[2603]! } - public var GroupPermission_EditingDisabled: String { return self._s[2604]! } - public var Wallet_Month_ShortSeptember: String { return self._s[2606]! } + public var Channel_DiscussionGroup_LinkGroup: String { return self._s[2598]! } + public var SettingsSearch_Synonyms_Notifications_InAppNotificationsVibrate: String { return self._s[2599]! } + public var Passport_RequestedInformation: String { return self._s[2600]! } + public var Login_PhoneAndCountryHelp: String { return self._s[2601]! } + public var Conversation_EncryptionProcessing: String { return self._s[2603]! } + public var Notifications_PermissionsSuppressWarningTitle: String { return self._s[2604]! } + public var PhotoEditor_EnhanceTool: String { return self._s[2606]! } + public var Channel_Setup_Title: String { return self._s[2607]! } + public var Conversation_SearchPlaceholder: String { return self._s[2608]! } + public var OldChannels_GroupEmptyFormat: String { return self._s[2609]! } + public var AccessDenied_LocationAlwaysDenied: String { return self._s[2610]! } + public var Checkout_ErrorGeneric: String { return self._s[2611]! } + public var Passport_Language_hu: String { return self._s[2612]! } + public var GroupPermission_EditingDisabled: String { return self._s[2613]! } + public var Wallet_Month_ShortSeptember: String { return self._s[2615]! } public func Passport_Identity_UploadOneOfScan(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2607]!, self._r[2607]!, [_0]) + return formatWithArgumentRanges(self._s[2616]!, self._r[2616]!, [_0]) } public func PUSH_MESSAGE(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2610]!, self._r[2610]!, [_1]) + return formatWithArgumentRanges(self._s[2619]!, self._r[2619]!, [_1]) } - public var ChatList_DeleteSavedMessagesConfirmationTitle: String { return self._s[2611]! } + public var ChatList_DeleteSavedMessagesConfirmationTitle: String { return self._s[2620]! } public func UserInfo_BlockConfirmationTitle(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2612]!, self._r[2612]!, [_0]) + return formatWithArgumentRanges(self._s[2621]!, self._r[2621]!, [_0]) } - public var Conversation_CloudStorageInfo_Title: String { return self._s[2613]! } - public var Group_Location_Info: String { return self._s[2614]! } - public var PhotoEditor_CropAspectRatioSquare: String { return self._s[2615]! } - public var Permissions_PeopleNearbyAllow_v0: String { return self._s[2616]! } + public var Conversation_CloudStorageInfo_Title: String { return self._s[2622]! } + public var Group_Location_Info: String { return self._s[2623]! } + public var PhotoEditor_CropAspectRatioSquare: String { return self._s[2624]! } + public var Permissions_PeopleNearbyAllow_v0: String { return self._s[2625]! } public func Notification_Exceptions_MutedUntil(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2617]!, self._r[2617]!, [_0]) + return formatWithArgumentRanges(self._s[2626]!, self._r[2626]!, [_0]) } - public var Conversation_ClearPrivateHistory: String { return self._s[2618]! } - public var ContactInfo_PhoneLabelHome: String { return self._s[2619]! } - public var Appearance_RemoveThemeConfirmation: String { return self._s[2620]! } - public var PrivacySettings_LastSeenContacts: String { return self._s[2621]! } + public var Conversation_ClearPrivateHistory: String { return self._s[2627]! } + public var ContactInfo_PhoneLabelHome: String { return self._s[2628]! } + public var Appearance_RemoveThemeConfirmation: String { return self._s[2629]! } + public var PrivacySettings_LastSeenContacts: String { return self._s[2630]! } public func ChangePhone_ErrorOccupied(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2622]!, self._r[2622]!, [_0]) + return formatWithArgumentRanges(self._s[2631]!, self._r[2631]!, [_0]) } public func Notification_PinnedQuizMessage(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2623]!, self._r[2623]!, [_0]) + return formatWithArgumentRanges(self._s[2632]!, self._r[2632]!, [_0]) } - public var Passport_Language_cs: String { return self._s[2624]! } - public var Message_PinnedAnimationMessage: String { return self._s[2626]! } - public var Passport_Identity_ReverseSideHelp: String { return self._s[2628]! } - public var SettingsSearch_Synonyms_Data_Storage_Title: String { return self._s[2629]! } - public var Wallet_Info_TransactionTo: String { return self._s[2631]! } - public var ChatList_DeleteForEveryoneConfirmationText: String { return self._s[2632]! } - public var SettingsSearch_Synonyms_Privacy_PasscodeAndTouchId: String { return self._s[2633]! } - public var Embed_PlayingInPIP: String { return self._s[2634]! } - public var Appearance_ThemePreview_Chat_3_TextWithLink: String { return self._s[2635]! } - public var AutoNightTheme_ScheduleSection: String { return self._s[2636]! } + public var Passport_Language_cs: String { return self._s[2633]! } + public var Message_PinnedAnimationMessage: String { return self._s[2635]! } + public var Passport_Identity_ReverseSideHelp: String { return self._s[2637]! } + public var SettingsSearch_Synonyms_Data_Storage_Title: String { return self._s[2638]! } + public var Wallet_Info_TransactionTo: String { return self._s[2640]! } + public var Stats_ViewsBySourceTitle: String { return self._s[2641]! } + public var ChatList_DeleteForEveryoneConfirmationText: String { return self._s[2642]! } + public var SettingsSearch_Synonyms_Privacy_PasscodeAndTouchId: String { return self._s[2643]! } + public var Embed_PlayingInPIP: String { return self._s[2644]! } + public var Appearance_ThemePreview_Chat_3_TextWithLink: String { return self._s[2645]! } + public var AutoNightTheme_ScheduleSection: String { return self._s[2646]! } public func Call_EmojiDescription(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2637]!, self._r[2637]!, [_0]) + return formatWithArgumentRanges(self._s[2647]!, self._r[2647]!, [_0]) } - public var MediaPicker_LivePhotoDescription: String { return self._s[2638]! } + public var MediaPicker_LivePhotoDescription: String { return self._s[2648]! } public func Channel_AdminLog_MessageRestrictedName(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2639]!, self._r[2639]!, [_1]) + return formatWithArgumentRanges(self._s[2649]!, self._r[2649]!, [_1]) } - public var Notification_PaymentSent: String { return self._s[2640]! } - public var PhotoEditor_CurvesGreen: String { return self._s[2641]! } - public var Notification_Exceptions_PreviewAlwaysOff: String { return self._s[2642]! } - public var AutoNightTheme_System: String { return self._s[2643]! } - public var SaveIncomingPhotosSettings_Title: String { return self._s[2644]! } - public var CreatePoll_QuizTitle: String { return self._s[2645]! } - public var NotificationSettings_ShowNotificationsAllAccounts: String { return self._s[2646]! } - public var VoiceOver_Chat_PagePreview: String { return self._s[2647]! } + public var Notification_PaymentSent: String { return self._s[2650]! } + public var PhotoEditor_CurvesGreen: String { return self._s[2651]! } + public var Notification_Exceptions_PreviewAlwaysOff: String { return self._s[2652]! } + public var AutoNightTheme_System: String { return self._s[2653]! } + public var SaveIncomingPhotosSettings_Title: String { return self._s[2654]! } + public var CreatePoll_QuizTitle: String { return self._s[2655]! } + public var NotificationSettings_ShowNotificationsAllAccounts: String { return self._s[2656]! } + public var VoiceOver_Chat_PagePreview: String { return self._s[2657]! } public func PUSH_MESSAGE_SCREENSHOT(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2650]!, self._r[2650]!, [_1]) + return formatWithArgumentRanges(self._s[2660]!, self._r[2660]!, [_1]) } public func PUSH_MESSAGE_PHOTO_SECRET(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2651]!, self._r[2651]!, [_1]) + return formatWithArgumentRanges(self._s[2661]!, self._r[2661]!, [_1]) } public func ApplyLanguage_UnsufficientDataText(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2652]!, self._r[2652]!, [_1]) + return formatWithArgumentRanges(self._s[2662]!, self._r[2662]!, [_1]) } - public var NetworkUsageSettings_CallDataSection: String { return self._s[2654]! } - public var PasscodeSettings_HelpTop: String { return self._s[2655]! } - public var Conversation_WalletRequiredTitle: String { return self._s[2656]! } - public var PeerInfo_AddToContacts: String { return self._s[2657]! } - public var Group_OwnershipTransfer_ErrorAdminsTooMuch: String { return self._s[2658]! } - public var Passport_Address_TypeRentalAgreement: String { return self._s[2659]! } - public var EditTheme_ShortLink: String { return self._s[2660]! } - public var Theme_Colors_ColorWallpaperWarning: String { return self._s[2661]! } - public var ProxyServer_VoiceOver_Active: String { return self._s[2662]! } - public var ReportPeer_ReasonOther_Placeholder: String { return self._s[2663]! } - public var CheckoutInfo_ErrorPhoneInvalid: String { return self._s[2664]! } - public var Call_Accept: String { return self._s[2666]! } - public var GroupRemoved_RemoveInfo: String { return self._s[2667]! } - public var Month_GenMarch: String { return self._s[2669]! } - public var PhotoEditor_ShadowsTool: String { return self._s[2670]! } - public var LoginPassword_Title: String { return self._s[2671]! } - public var Call_End: String { return self._s[2672]! } - public var Watch_Conversation_GroupInfo: String { return self._s[2673]! } - public var VoiceOver_Chat_Contact: String { return self._s[2674]! } - public var EditTheme_Create_Preview_IncomingText: String { return self._s[2675]! } - public var CallSettings_Always: String { return self._s[2676]! } - public var CallFeedback_Success: String { return self._s[2677]! } - public var TwoStepAuth_SetupHint: String { return self._s[2678]! } + public var NetworkUsageSettings_CallDataSection: String { return self._s[2664]! } + public var PasscodeSettings_HelpTop: String { return self._s[2665]! } + public var Conversation_WalletRequiredTitle: String { return self._s[2666]! } + public var PeerInfo_AddToContacts: String { return self._s[2667]! } + public var Group_OwnershipTransfer_ErrorAdminsTooMuch: String { return self._s[2668]! } + public var Passport_Address_TypeRentalAgreement: String { return self._s[2669]! } + public var EditTheme_ShortLink: String { return self._s[2670]! } + public var Theme_Colors_ColorWallpaperWarning: String { return self._s[2671]! } + public var ProxyServer_VoiceOver_Active: String { return self._s[2672]! } + public var ReportPeer_ReasonOther_Placeholder: String { return self._s[2673]! } + public var CheckoutInfo_ErrorPhoneInvalid: String { return self._s[2674]! } + public var Call_Accept: String { return self._s[2676]! } + public var GroupRemoved_RemoveInfo: String { return self._s[2677]! } + public var Month_GenMarch: String { return self._s[2679]! } + public var PhotoEditor_ShadowsTool: String { return self._s[2680]! } + public var LoginPassword_Title: String { return self._s[2681]! } + public var Call_End: String { return self._s[2682]! } + public var Watch_Conversation_GroupInfo: String { return self._s[2683]! } + public var VoiceOver_Chat_Contact: String { return self._s[2684]! } + public var EditTheme_Create_Preview_IncomingText: String { return self._s[2685]! } + public var CallSettings_Always: String { return self._s[2686]! } + public var CallFeedback_Success: String { return self._s[2687]! } + public var TwoStepAuth_SetupHint: String { return self._s[2688]! } public func AddContact_ContactWillBeSharedAfterMutual(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2679]!, self._r[2679]!, [_1]) + return formatWithArgumentRanges(self._s[2689]!, self._r[2689]!, [_1]) } - public var ConversationProfile_UsersTooMuchError: String { return self._s[2680]! } - public var PeerInfo_ButtonAddMember: String { return self._s[2681]! } - public var Login_PhoneTitle: String { return self._s[2682]! } - public var Passport_FieldPhoneHelp: String { return self._s[2683]! } - public var Weekday_ShortSunday: String { return self._s[2684]! } - public var Passport_InfoFAQ_URL: String { return self._s[2685]! } - public var ContactInfo_Job: String { return self._s[2687]! } - public var UserInfo_InviteBotToGroup: String { return self._s[2688]! } - public var Appearance_ThemeCarouselNightBlue: String { return self._s[2689]! } - public var CreatePoll_QuizTip: String { return self._s[2690]! } - public var TwoFactorSetup_Email_Text: String { return self._s[2691]! } - public var TwoStepAuth_PasswordRemovePassportConfirmation: String { return self._s[2692]! } - public var Invite_ChannelsTooMuch: String { return self._s[2693]! } - public var Wallet_Send_ConfirmationConfirm: String { return self._s[2694]! } - public var Wallet_TransactionInfo_OtherFeeInfo: String { return self._s[2695]! } - public var SettingsSearch_Synonyms_Notifications_InAppNotificationsPreview: String { return self._s[2696]! } - public var Wallet_Receive_AmountText: String { return self._s[2697]! } - public var Passport_DeletePersonalDetailsConfirmation: String { return self._s[2698]! } - public var CallFeedback_ReasonNoise: String { return self._s[2699]! } - public var Appearance_AppIconDefault: String { return self._s[2701]! } - public var Passport_Identity_AddInternalPassport: String { return self._s[2702]! } - public var MediaPicker_AddCaption: String { return self._s[2703]! } - public var CallSettings_TabIconDescription: String { return self._s[2704]! } + public var ConversationProfile_UsersTooMuchError: String { return self._s[2690]! } + public var PeerInfo_ButtonAddMember: String { return self._s[2691]! } + public var Login_PhoneTitle: String { return self._s[2692]! } + public var Passport_FieldPhoneHelp: String { return self._s[2693]! } + public var Weekday_ShortSunday: String { return self._s[2694]! } + public var Passport_InfoFAQ_URL: String { return self._s[2695]! } + public var ContactInfo_Job: String { return self._s[2697]! } + public var UserInfo_InviteBotToGroup: String { return self._s[2698]! } + public var Appearance_ThemeCarouselNightBlue: String { return self._s[2699]! } + public var CreatePoll_QuizTip: String { return self._s[2700]! } + public var TwoFactorSetup_Email_Text: String { return self._s[2701]! } + public var TwoStepAuth_PasswordRemovePassportConfirmation: String { return self._s[2702]! } + public var Invite_ChannelsTooMuch: String { return self._s[2703]! } + public var Wallet_Send_ConfirmationConfirm: String { return self._s[2704]! } + public var Wallet_TransactionInfo_OtherFeeInfo: String { return self._s[2705]! } + public var SettingsSearch_Synonyms_Notifications_InAppNotificationsPreview: String { return self._s[2706]! } + public var Wallet_Receive_AmountText: String { return self._s[2707]! } + public var Passport_DeletePersonalDetailsConfirmation: String { return self._s[2708]! } + public var CallFeedback_ReasonNoise: String { return self._s[2709]! } + public var Appearance_AppIconDefault: String { return self._s[2711]! } + public var Passport_Identity_AddInternalPassport: String { return self._s[2712]! } + public var MediaPicker_AddCaption: String { return self._s[2713]! } + public var CallSettings_TabIconDescription: String { return self._s[2714]! } public func VoiceOver_Chat_Caption(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2705]!, self._r[2705]!, [_0]) + return formatWithArgumentRanges(self._s[2715]!, self._r[2715]!, [_0]) } - public var IntentsSettings_SuggestedChatsGroups: String { return self._s[2706]! } + public var IntentsSettings_SuggestedChatsGroups: String { return self._s[2716]! } public func Map_SearchNoResultsDescription(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2707]!, self._r[2707]!, [_0]) + return formatWithArgumentRanges(self._s[2717]!, self._r[2717]!, [_0]) } - public var ChatList_UndoArchiveHiddenTitle: String { return self._s[2708]! } - public var Privacy_GroupsAndChannels_AlwaysAllow: String { return self._s[2709]! } - public var Passport_Identity_TypePersonalDetails: String { return self._s[2710]! } - public var DialogList_SearchSectionRecent: String { return self._s[2711]! } - public var PrivacyPolicy_DeclineMessage: String { return self._s[2712]! } - public var CreatePoll_Anonymous: String { return self._s[2713]! } - public var LogoutOptions_ClearCacheText: String { return self._s[2716]! } - public var LastSeen_WithinAWeek: String { return self._s[2717]! } - public var ChannelMembers_GroupAdminsTitle: String { return self._s[2718]! } - public var Conversation_CloudStorage_ChatStatus: String { return self._s[2720]! } - public var VoiceOver_Media_PlaybackRateNormal: String { return self._s[2721]! } + public var ChatList_UndoArchiveHiddenTitle: String { return self._s[2718]! } + public var Privacy_GroupsAndChannels_AlwaysAllow: String { return self._s[2719]! } + public var Passport_Identity_TypePersonalDetails: String { return self._s[2720]! } + public var DialogList_SearchSectionRecent: String { return self._s[2721]! } + public var PrivacyPolicy_DeclineMessage: String { return self._s[2722]! } + public var CreatePoll_Anonymous: String { return self._s[2723]! } + public var LogoutOptions_ClearCacheText: String { return self._s[2726]! } + public var LastSeen_WithinAWeek: String { return self._s[2727]! } + public var ChannelMembers_GroupAdminsTitle: String { return self._s[2728]! } + public var Conversation_CloudStorage_ChatStatus: String { return self._s[2730]! } + public var VoiceOver_Media_PlaybackRateNormal: String { return self._s[2731]! } public func AddContact_SharedContactExceptionInfo(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2722]!, self._r[2722]!, [_0]) + return formatWithArgumentRanges(self._s[2732]!, self._r[2732]!, [_0]) } - public var Passport_Address_TypeResidentialAddress: String { return self._s[2723]! } - public var Conversation_StatusLeftGroup: String { return self._s[2724]! } - public var SocksProxySetup_ProxyDetailsTitle: String { return self._s[2725]! } - public var SettingsSearch_Synonyms_Calls_Title: String { return self._s[2727]! } - public var GroupPermission_AddSuccess: String { return self._s[2728]! } - public var PhotoEditor_BlurToolRadial: String { return self._s[2730]! } - public var Conversation_ContextMenuCopy: String { return self._s[2731]! } - public var AccessDenied_CallMicrophone: String { return self._s[2732]! } + public var Passport_Address_TypeResidentialAddress: String { return self._s[2733]! } + public var Conversation_StatusLeftGroup: String { return self._s[2734]! } + public var SocksProxySetup_ProxyDetailsTitle: String { return self._s[2735]! } + public var SettingsSearch_Synonyms_Calls_Title: String { return self._s[2737]! } + public var GroupPermission_AddSuccess: String { return self._s[2738]! } + public var PhotoEditor_BlurToolRadial: String { return self._s[2740]! } + public var Conversation_ContextMenuCopy: String { return self._s[2741]! } + public var AccessDenied_CallMicrophone: String { return self._s[2742]! } public func Time_PreciseDate_m2(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2733]!, self._r[2733]!, [_1, _2, _3]) + return formatWithArgumentRanges(self._s[2743]!, self._r[2743]!, [_1, _2, _3]) } - public var Login_InvalidFirstNameError: String { return self._s[2734]! } - public var Notifications_Badge_CountUnreadMessages_InfoOn: String { return self._s[2735]! } - public var Checkout_PaymentMethod_New: String { return self._s[2736]! } - public var ShareMenu_CopyShareLinkGame: String { return self._s[2737]! } - public var PhotoEditor_QualityTool: String { return self._s[2738]! } - public var Login_SendCodeViaSms: String { return self._s[2739]! } - public var SettingsSearch_Synonyms_Privacy_DeleteAccountIfAwayFor: String { return self._s[2740]! } - public var Chat_SlowmodeAttachmentLimitReached: String { return self._s[2741]! } - public var Wallet_Receive_CopyAddress: String { return self._s[2742]! } - public var Login_EmailNotConfiguredError: String { return self._s[2743]! } - public var SocksProxySetup_Status: String { return self._s[2744]! } - public var Conversation_ScheduleMessage_SendWhenOnline: String { return self._s[2745]! } - public var PrivacyPolicy_Accept: String { return self._s[2746]! } - public var Notifications_ExceptionsMessagePlaceholder: String { return self._s[2747]! } - public var Appearance_AppIconClassicX: String { return self._s[2748]! } + public var Login_InvalidFirstNameError: String { return self._s[2744]! } + public var Notifications_Badge_CountUnreadMessages_InfoOn: String { return self._s[2745]! } + public var Checkout_PaymentMethod_New: String { return self._s[2746]! } + public var ShareMenu_CopyShareLinkGame: String { return self._s[2747]! } + public var PhotoEditor_QualityTool: String { return self._s[2748]! } + public var Login_SendCodeViaSms: String { return self._s[2749]! } + public var SettingsSearch_Synonyms_Privacy_DeleteAccountIfAwayFor: String { return self._s[2750]! } + public var Chat_SlowmodeAttachmentLimitReached: String { return self._s[2751]! } + public var Wallet_Receive_CopyAddress: String { return self._s[2752]! } + public var Login_EmailNotConfiguredError: String { return self._s[2753]! } + public var SocksProxySetup_Status: String { return self._s[2754]! } + public var Conversation_ScheduleMessage_SendWhenOnline: String { return self._s[2755]! } + public var PrivacyPolicy_Accept: String { return self._s[2756]! } + public var Notifications_ExceptionsMessagePlaceholder: String { return self._s[2757]! } + public var Appearance_AppIconClassicX: String { return self._s[2758]! } public func PUSH_CHAT_MESSAGE_TEXT(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2749]!, self._r[2749]!, [_1, _2, _3]) + return formatWithArgumentRanges(self._s[2759]!, self._r[2759]!, [_1, _2, _3]) } - public var OwnershipTransfer_SecurityRequirements: String { return self._s[2750]! } - public var InfoPlist_NSLocationAlwaysUsageDescription: String { return self._s[2752]! } - public var AutoNightTheme_Automatic: String { return self._s[2753]! } - public var Channel_Username_InvalidStartsWithNumber: String { return self._s[2754]! } - public var Privacy_ContactsSyncHelp: String { return self._s[2755]! } - public var Cache_Help: String { return self._s[2756]! } - public var Group_ErrorAccessDenied: String { return self._s[2757]! } - public var Passport_Language_fa: String { return self._s[2758]! } - public var Wallet_Intro_Text: String { return self._s[2759]! } - public var Login_ResetAccountProtected_TimerTitle: String { return self._s[2760]! } - public var VoiceOver_Chat_YourVideoMessage: String { return self._s[2761]! } - public var PrivacySettings_LastSeen: String { return self._s[2762]! } + public var OwnershipTransfer_SecurityRequirements: String { return self._s[2760]! } + public var InfoPlist_NSLocationAlwaysUsageDescription: String { return self._s[2762]! } + public var AutoNightTheme_Automatic: String { return self._s[2763]! } + public var Channel_Username_InvalidStartsWithNumber: String { return self._s[2764]! } + public var Privacy_ContactsSyncHelp: String { return self._s[2765]! } + public var Cache_Help: String { return self._s[2766]! } + public var Group_ErrorAccessDenied: String { return self._s[2767]! } + public var Passport_Language_fa: String { return self._s[2768]! } + public var Wallet_Intro_Text: String { return self._s[2769]! } + public var Login_ResetAccountProtected_TimerTitle: String { return self._s[2770]! } + public var VoiceOver_Chat_YourVideoMessage: String { return self._s[2771]! } + public var PrivacySettings_LastSeen: String { return self._s[2772]! } public func DialogList_MultipleTyping(_ _0: String, _ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2763]!, self._r[2763]!, [_0, _1]) + return formatWithArgumentRanges(self._s[2773]!, self._r[2773]!, [_0, _1]) } - public var Wallet_Configuration_Apply: String { return self._s[2767]! } - public var Preview_SaveGif: String { return self._s[2768]! } - public var SettingsSearch_Synonyms_Privacy_TwoStepAuth: String { return self._s[2769]! } - public var Profile_About: String { return self._s[2770]! } - public var Channel_About_Placeholder: String { return self._s[2771]! } - public var Login_InfoTitle: String { return self._s[2772]! } + public var Wallet_Configuration_Apply: String { return self._s[2777]! } + public var Preview_SaveGif: String { return self._s[2778]! } + public var SettingsSearch_Synonyms_Privacy_TwoStepAuth: String { return self._s[2779]! } + public var Profile_About: String { return self._s[2780]! } + public var Channel_About_Placeholder: String { return self._s[2781]! } + public var Login_InfoTitle: String { return self._s[2782]! } public func TwoStepAuth_SetupPendingEmail(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2773]!, self._r[2773]!, [_0]) + return formatWithArgumentRanges(self._s[2783]!, self._r[2783]!, [_0]) } - public var EditTheme_Expand_Preview_IncomingReplyText: String { return self._s[2774]! } - public var Watch_Suggestion_CantTalk: String { return self._s[2776]! } - public var ContactInfo_Title: String { return self._s[2777]! } - public var Media_ShareThisVideo: String { return self._s[2778]! } - public var Weekday_ShortFriday: String { return self._s[2779]! } - public var AccessDenied_Contacts: String { return self._s[2781]! } - public var Notification_CallIncomingShort: String { return self._s[2782]! } - public var Group_Setup_TypePublic: String { return self._s[2783]! } - public var Notifications_MessageNotificationsExceptions: String { return self._s[2784]! } - public var Notifications_Badge_IncludeChannels: String { return self._s[2785]! } - public var Notifications_MessageNotificationsPreview: String { return self._s[2788]! } - public var ConversationProfile_ErrorCreatingConversation: String { return self._s[2789]! } - public var Group_ErrorAddTooMuchBots: String { return self._s[2790]! } - public var Privacy_GroupsAndChannels_CustomShareHelp: String { return self._s[2791]! } - public var Permissions_CellularDataAllowInSettings_v0: String { return self._s[2792]! } + public var EditTheme_Expand_Preview_IncomingReplyText: String { return self._s[2784]! } + public var Watch_Suggestion_CantTalk: String { return self._s[2786]! } + public var ContactInfo_Title: String { return self._s[2787]! } + public var Media_ShareThisVideo: String { return self._s[2788]! } + public var Weekday_ShortFriday: String { return self._s[2789]! } + public var AccessDenied_Contacts: String { return self._s[2791]! } + public var Notification_CallIncomingShort: String { return self._s[2792]! } + public var Group_Setup_TypePublic: String { return self._s[2793]! } + public var Notifications_MessageNotificationsExceptions: String { return self._s[2794]! } + public var Notifications_Badge_IncludeChannels: String { return self._s[2795]! } + public var Notifications_MessageNotificationsPreview: String { return self._s[2798]! } + public var ConversationProfile_ErrorCreatingConversation: String { return self._s[2799]! } + public var Group_ErrorAddTooMuchBots: String { return self._s[2800]! } + public var Privacy_GroupsAndChannels_CustomShareHelp: String { return self._s[2801]! } + public var Permissions_CellularDataAllowInSettings_v0: String { return self._s[2802]! } public func Wallet_SecureStorageChanged_BiometryText(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2793]!, self._r[2793]!, [_0]) + return formatWithArgumentRanges(self._s[2803]!, self._r[2803]!, [_0]) } - public var DialogList_Typing: String { return self._s[2794]! } - public var CallFeedback_IncludeLogs: String { return self._s[2796]! } - public var Checkout_Phone: String { return self._s[2798]! } - public var Login_InfoFirstNamePlaceholder: String { return self._s[2801]! } - public var Privacy_Calls_Integration: String { return self._s[2802]! } - public var Notifications_PermissionsAllow: String { return self._s[2803]! } - public var TwoStepAuth_AddHintDescription: String { return self._s[2808]! } - public var Settings_ChatSettings: String { return self._s[2809]! } - public var Conversation_SendingOptionsTooltip: String { return self._s[2810]! } + public var DialogList_Typing: String { return self._s[2804]! } + public var CallFeedback_IncludeLogs: String { return self._s[2806]! } + public var Checkout_Phone: String { return self._s[2808]! } + public var Login_InfoFirstNamePlaceholder: String { return self._s[2811]! } + public var Privacy_Calls_Integration: String { return self._s[2812]! } + public var Notifications_PermissionsAllow: String { return self._s[2813]! } + public var TwoStepAuth_AddHintDescription: String { return self._s[2818]! } + public var Settings_ChatSettings: String { return self._s[2819]! } + public var Conversation_SendingOptionsTooltip: String { return self._s[2820]! } public func UserInfo_StartSecretChatConfirmation(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2812]!, self._r[2812]!, [_0]) + return formatWithArgumentRanges(self._s[2822]!, self._r[2822]!, [_0]) } public func Channel_AdminLog_MessageInvitedNameUsername(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2813]!, self._r[2813]!, [_1, _2]) + return formatWithArgumentRanges(self._s[2823]!, self._r[2823]!, [_1, _2]) } - public var GroupRemoved_DeleteUser: String { return self._s[2815]! } + public var GroupRemoved_DeleteUser: String { return self._s[2825]! } public func Channel_AdminLog_PollStopped(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2816]!, self._r[2816]!, [_0]) + return formatWithArgumentRanges(self._s[2826]!, self._r[2826]!, [_0]) } public func PUSH_MESSAGE_PHOTO(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2817]!, self._r[2817]!, [_1]) + return formatWithArgumentRanges(self._s[2827]!, self._r[2827]!, [_1]) } - public var Login_ContinueWithLocalization: String { return self._s[2818]! } - public var Watch_Message_ForwardedFrom: String { return self._s[2819]! } - public var TwoStepAuth_EnterEmailCode: String { return self._s[2821]! } - public var Conversation_Unblock: String { return self._s[2822]! } - public var PrivacySettings_DataSettings: String { return self._s[2823]! } - public var WallpaperPreview_PatternPaternApply: String { return self._s[2824]! } - public var Group_PublicLink_Info: String { return self._s[2825]! } + public var Login_ContinueWithLocalization: String { return self._s[2828]! } + public var Watch_Message_ForwardedFrom: String { return self._s[2829]! } + public var TwoStepAuth_EnterEmailCode: String { return self._s[2831]! } + public var Conversation_Unblock: String { return self._s[2832]! } + public var PrivacySettings_DataSettings: String { return self._s[2833]! } + public var WallpaperPreview_PatternPaternApply: String { return self._s[2834]! } + public var Group_PublicLink_Info: String { return self._s[2835]! } public func Wallet_Time_PreciseDate_m1(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2826]!, self._r[2826]!, [_1, _2, _3]) + return formatWithArgumentRanges(self._s[2836]!, self._r[2836]!, [_1, _2, _3]) } - public var Notifications_InAppNotificationsVibrate: String { return self._s[2827]! } + public var Notifications_InAppNotificationsVibrate: String { return self._s[2837]! } public func Privacy_GroupsAndChannels_InviteToChannelError(_ _0: String, _ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2828]!, self._r[2828]!, [_0, _1]) + return formatWithArgumentRanges(self._s[2838]!, self._r[2838]!, [_0, _1]) } - public var OldChannels_ChannelsHeader: String { return self._s[2830]! } - public var Wallet_RestoreFailed_CreateWallet: String { return self._s[2831]! } - public var PrivacySettings_Passcode: String { return self._s[2833]! } - public var Call_Mute: String { return self._s[2834]! } - public var Wallet_Weekday_Yesterday: String { return self._s[2835]! } - public var Passport_Language_dz: String { return self._s[2836]! } - public var Wallet_Receive_AmountHeader: String { return self._s[2837]! } - public var Wallet_TransactionInfo_OtherFeeInfoUrl: String { return self._s[2838]! } - public var Passport_Language_tk: String { return self._s[2839]! } + public var OldChannels_ChannelsHeader: String { return self._s[2840]! } + public var Wallet_RestoreFailed_CreateWallet: String { return self._s[2841]! } + public var PrivacySettings_Passcode: String { return self._s[2843]! } + public var Call_Mute: String { return self._s[2844]! } + public var Wallet_Weekday_Yesterday: String { return self._s[2845]! } + public var Passport_Language_dz: String { return self._s[2846]! } + public var Wallet_Receive_AmountHeader: String { return self._s[2847]! } + public var Wallet_TransactionInfo_OtherFeeInfoUrl: String { return self._s[2848]! } + public var Passport_Language_tk: String { return self._s[2849]! } public func Login_EmailCodeSubject(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2840]!, self._r[2840]!, [_0]) + return formatWithArgumentRanges(self._s[2850]!, self._r[2850]!, [_0]) } - public var Settings_Search: String { return self._s[2841]! } - public var Wallet_Month_ShortFebruary: String { return self._s[2842]! } - public var InfoPlist_NSPhotoLibraryUsageDescription: String { return self._s[2843]! } - public var Wallet_Configuration_SourceJSON: String { return self._s[2844]! } - public var Conversation_ContextMenuReply: String { return self._s[2845]! } - public var WallpaperSearch_ColorBrown: String { return self._s[2846]! } - public var Chat_AttachmentMultipleForwardDisabled: String { return self._s[2847]! } - public var Tour_Title1: String { return self._s[2848]! } - public var Wallet_Alert_Cancel: String { return self._s[2849]! } - public var Conversation_ClearGroupHistory: String { return self._s[2851]! } - public var Wallet_TransactionInfo_RecipientHeader: String { return self._s[2852]! } - public var WallpaperPreview_Motion: String { return self._s[2853]! } + public var Settings_Search: String { return self._s[2851]! } + public var Wallet_Month_ShortFebruary: String { return self._s[2852]! } + public var InfoPlist_NSPhotoLibraryUsageDescription: String { return self._s[2853]! } + public var Wallet_Configuration_SourceJSON: String { return self._s[2854]! } + public var Conversation_ContextMenuReply: String { return self._s[2855]! } + public var WallpaperSearch_ColorBrown: String { return self._s[2856]! } + public var Chat_AttachmentMultipleForwardDisabled: String { return self._s[2857]! } + public var Tour_Title1: String { return self._s[2858]! } + public var Wallet_Alert_Cancel: String { return self._s[2859]! } + public var Conversation_ClearGroupHistory: String { return self._s[2861]! } + public var Wallet_TransactionInfo_RecipientHeader: String { return self._s[2862]! } + public var WallpaperPreview_Motion: String { return self._s[2863]! } public func Checkout_PasswordEntry_Text(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2854]!, self._r[2854]!, [_0]) + return formatWithArgumentRanges(self._s[2864]!, self._r[2864]!, [_0]) } - public var Wallet_Configuration_ApplyErrorTextJSONInvalidData: String { return self._s[2855]! } - public var Call_RateCall: String { return self._s[2856]! } - public var Channel_AdminLog_BanSendStickersAndGifs: String { return self._s[2857]! } - public var Passport_PasswordCompleteSetup: String { return self._s[2858]! } - public var Conversation_InputTextSilentBroadcastPlaceholder: String { return self._s[2859]! } - public var UserInfo_LastNamePlaceholder: String { return self._s[2861]! } + public var Wallet_Configuration_ApplyErrorTextJSONInvalidData: String { return self._s[2865]! } + public var Call_RateCall: String { return self._s[2866]! } + public var Channel_AdminLog_BanSendStickersAndGifs: String { return self._s[2867]! } + public var Passport_PasswordCompleteSetup: String { return self._s[2868]! } + public var Conversation_InputTextSilentBroadcastPlaceholder: String { return self._s[2869]! } + public var UserInfo_LastNamePlaceholder: String { return self._s[2871]! } public func Login_WillCallYou(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2863]!, self._r[2863]!, [_0]) + return formatWithArgumentRanges(self._s[2873]!, self._r[2873]!, [_0]) } - public var Compose_Create: String { return self._s[2864]! } - public var Contacts_InviteToTelegram: String { return self._s[2865]! } - public var GroupInfo_Notifications: String { return self._s[2866]! } - public var ChatList_DeleteSavedMessagesConfirmationAction: String { return self._s[2868]! } - public var Message_PinnedLiveLocationMessage: String { return self._s[2869]! } - public var Month_GenApril: String { return self._s[2870]! } - public var Appearance_AutoNightTheme: String { return self._s[2871]! } - public var ChatSettings_AutomaticAudioDownload: String { return self._s[2873]! } - public var Login_CodeSentSms: String { return self._s[2875]! } + public var Compose_Create: String { return self._s[2874]! } + public var Contacts_InviteToTelegram: String { return self._s[2875]! } + public var GroupInfo_Notifications: String { return self._s[2876]! } + public var ChatList_DeleteSavedMessagesConfirmationAction: String { return self._s[2878]! } + public var Message_PinnedLiveLocationMessage: String { return self._s[2879]! } + public var Month_GenApril: String { return self._s[2880]! } + public var Appearance_AutoNightTheme: String { return self._s[2881]! } + public var ChatSettings_AutomaticAudioDownload: String { return self._s[2883]! } + public var Login_CodeSentSms: String { return self._s[2885]! } public func UserInfo_UnblockConfirmation(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2876]!, self._r[2876]!, [_0]) + return formatWithArgumentRanges(self._s[2886]!, self._r[2886]!, [_0]) } - public var EmptyGroupInfo_Line3: String { return self._s[2877]! } - public var LogoutOptions_ContactSupportText: String { return self._s[2878]! } - public var Passport_Language_hr: String { return self._s[2879]! } - public var Common_ActionNotAllowedError: String { return self._s[2880]! } + public var EmptyGroupInfo_Line3: String { return self._s[2887]! } + public var LogoutOptions_ContactSupportText: String { return self._s[2888]! } + public var Passport_Language_hr: String { return self._s[2889]! } + public var Common_ActionNotAllowedError: String { return self._s[2890]! } public func Channel_AdminLog_MessageRestrictedNewSetting(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2881]!, self._r[2881]!, [_0]) + return formatWithArgumentRanges(self._s[2891]!, self._r[2891]!, [_0]) } - public var GroupInfo_InviteLink_CopyLink: String { return self._s[2882]! } - public var Wallet_Info_TransactionFrom: String { return self._s[2883]! } - public var Wallet_Send_ErrorDecryptionFailed: String { return self._s[2884]! } - public var Conversation_InputTextBroadcastPlaceholder: String { return self._s[2885]! } - public var Privacy_SecretChatsTitle: String { return self._s[2886]! } - public var Notification_SecretChatMessageScreenshotSelf: String { return self._s[2888]! } - public var GroupInfo_AddUserLeftError: String { return self._s[2889]! } - public var AutoDownloadSettings_TypePrivateChats: String { return self._s[2890]! } - public var LogoutOptions_ContactSupportTitle: String { return self._s[2891]! } - public var Appearance_ThemePreview_Chat_7_Text: String { return self._s[2892]! } - public var Channel_AddBotErrorHaveRights: String { return self._s[2893]! } - public var Preview_DeleteGif: String { return self._s[2894]! } - public var GroupInfo_Permissions_Exceptions: String { return self._s[2895]! } - public var Group_ErrorNotMutualContact: String { return self._s[2896]! } - public var Notification_MessageLifetime5s: String { return self._s[2897]! } - public var Wallet_Send_OwnAddressAlertText: String { return self._s[2898]! } - public var OldChannels_ChannelFormat: String { return self._s[2899]! } + public var GroupInfo_InviteLink_CopyLink: String { return self._s[2892]! } + public var Wallet_Info_TransactionFrom: String { return self._s[2893]! } + public var Wallet_Send_ErrorDecryptionFailed: String { return self._s[2894]! } + public var Conversation_InputTextBroadcastPlaceholder: String { return self._s[2895]! } + public var Privacy_SecretChatsTitle: String { return self._s[2896]! } + public var Notification_SecretChatMessageScreenshotSelf: String { return self._s[2898]! } + public var GroupInfo_AddUserLeftError: String { return self._s[2899]! } + public var AutoDownloadSettings_TypePrivateChats: String { return self._s[2900]! } + public var LogoutOptions_ContactSupportTitle: String { return self._s[2901]! } + public var Appearance_ThemePreview_Chat_7_Text: String { return self._s[2902]! } + public var Channel_AddBotErrorHaveRights: String { return self._s[2903]! } + public var Preview_DeleteGif: String { return self._s[2904]! } + public var GroupInfo_Permissions_Exceptions: String { return self._s[2905]! } + public var Group_ErrorNotMutualContact: String { return self._s[2906]! } + public var Notification_MessageLifetime5s: String { return self._s[2907]! } + public var Wallet_Send_OwnAddressAlertText: String { return self._s[2908]! } + public var OldChannels_ChannelFormat: String { return self._s[2909]! } public func Watch_LastSeen_AtDate(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2900]!, self._r[2900]!, [_0]) + return formatWithArgumentRanges(self._s[2910]!, self._r[2910]!, [_0]) } - public var VoiceOver_Chat_Video: String { return self._s[2901]! } - public var Channel_OwnershipTransfer_ErrorPublicChannelsTooMuch: String { return self._s[2903]! } - public var ReportSpam_DeleteThisChat: String { return self._s[2904]! } - public var Passport_Address_AddBankStatement: String { return self._s[2905]! } - public var Notification_CallIncoming: String { return self._s[2906]! } - public var Wallet_Words_NotDoneTitle: String { return self._s[2907]! } - public var Compose_NewGroupTitle: String { return self._s[2908]! } - public var TwoStepAuth_RecoveryCodeHelp: String { return self._s[2910]! } - public var Passport_Address_Postcode: String { return self._s[2912]! } + public var VoiceOver_Chat_Video: String { return self._s[2911]! } + public var Channel_OwnershipTransfer_ErrorPublicChannelsTooMuch: String { return self._s[2913]! } + public var ReportSpam_DeleteThisChat: String { return self._s[2914]! } + public var Passport_Address_AddBankStatement: String { return self._s[2915]! } + public var Notification_CallIncoming: String { return self._s[2916]! } + public var Wallet_Words_NotDoneTitle: String { return self._s[2917]! } + public var Compose_NewGroupTitle: String { return self._s[2918]! } + public var TwoStepAuth_RecoveryCodeHelp: String { return self._s[2920]! } + public var Passport_Address_Postcode: String { return self._s[2922]! } public func LastSeen_YesterdayAt(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2913]!, self._r[2913]!, [_0]) + return formatWithArgumentRanges(self._s[2923]!, self._r[2923]!, [_0]) } - public var Checkout_NewCard_SaveInfoHelp: String { return self._s[2914]! } - public var Wallet_Month_ShortOctober: String { return self._s[2915]! } - public var VoiceOver_Chat_YourMusic: String { return self._s[2916]! } - public var WallpaperColors_Title: String { return self._s[2917]! } - public var SocksProxySetup_ShareQRCodeInfo: String { return self._s[2918]! } - public var VoiceOver_MessageContextForward: String { return self._s[2919]! } - public var GroupPermission_Duration: String { return self._s[2920]! } + public var Checkout_NewCard_SaveInfoHelp: String { return self._s[2924]! } + public var Wallet_Month_ShortOctober: String { return self._s[2925]! } + public var VoiceOver_Chat_YourMusic: String { return self._s[2926]! } + public var WallpaperColors_Title: String { return self._s[2927]! } + public var SocksProxySetup_ShareQRCodeInfo: String { return self._s[2928]! } + public var VoiceOver_MessageContextForward: String { return self._s[2929]! } + public var GroupPermission_Duration: String { return self._s[2930]! } public func Cache_Clear(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2921]!, self._r[2921]!, [_0]) + return formatWithArgumentRanges(self._s[2931]!, self._r[2931]!, [_0]) } - public var Bot_GroupStatusDoesNotReadHistory: String { return self._s[2922]! } - public var Username_Placeholder: String { return self._s[2923]! } - public var CallFeedback_WhatWentWrong: String { return self._s[2924]! } - public var Passport_FieldAddressUploadHelp: String { return self._s[2925]! } - public var Permissions_NotificationsAllowInSettings_v0: String { return self._s[2926]! } + public var Bot_GroupStatusDoesNotReadHistory: String { return self._s[2932]! } + public var Username_Placeholder: String { return self._s[2933]! } + public var CallFeedback_WhatWentWrong: String { return self._s[2934]! } + public var Passport_FieldAddressUploadHelp: String { return self._s[2935]! } + public var Permissions_NotificationsAllowInSettings_v0: String { return self._s[2936]! } public func Channel_AdminLog_MessageChangedUnlinkedChannel(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2928]!, self._r[2928]!, [_1, _2]) + return formatWithArgumentRanges(self._s[2938]!, self._r[2938]!, [_1, _2]) } - public var Passport_PasswordDescription: String { return self._s[2929]! } - public var Channel_MessagePhotoUpdated: String { return self._s[2930]! } - public var MediaPicker_TapToUngroupDescription: String { return self._s[2931]! } - public var SettingsSearch_Synonyms_Notifications_BadgeCountUnreadMessages: String { return self._s[2932]! } - public var AttachmentMenu_PhotoOrVideo: String { return self._s[2933]! } - public var Conversation_ContextMenuMore: String { return self._s[2934]! } - public var Privacy_PaymentsClearInfo: String { return self._s[2935]! } - public var CallSettings_TabIcon: String { return self._s[2936]! } - public var KeyCommand_Find: String { return self._s[2937]! } - public var ClearCache_FreeSpaceDescription: String { return self._s[2938]! } - public var Appearance_ThemePreview_ChatList_7_Text: String { return self._s[2939]! } - public var EditTheme_Edit_Preview_IncomingText: String { return self._s[2940]! } - public var Message_PinnedGame: String { return self._s[2941]! } - public var VoiceOver_Chat_ForwardedFromYou: String { return self._s[2942]! } - public var Notifications_Badge_CountUnreadMessages_InfoOff: String { return self._s[2944]! } - public var Login_CallRequestState2: String { return self._s[2946]! } - public var CheckoutInfo_ReceiverInfoNamePlaceholder: String { return self._s[2948]! } + public var Passport_PasswordDescription: String { return self._s[2939]! } + public var Channel_MessagePhotoUpdated: String { return self._s[2940]! } + public var MediaPicker_TapToUngroupDescription: String { return self._s[2941]! } + public var SettingsSearch_Synonyms_Notifications_BadgeCountUnreadMessages: String { return self._s[2942]! } + public var AttachmentMenu_PhotoOrVideo: String { return self._s[2943]! } + public var Conversation_ContextMenuMore: String { return self._s[2944]! } + public var Privacy_PaymentsClearInfo: String { return self._s[2945]! } + public var CallSettings_TabIcon: String { return self._s[2946]! } + public var KeyCommand_Find: String { return self._s[2947]! } + public var ClearCache_FreeSpaceDescription: String { return self._s[2948]! } + public var Appearance_ThemePreview_ChatList_7_Text: String { return self._s[2949]! } + public var EditTheme_Edit_Preview_IncomingText: String { return self._s[2950]! } + public var Message_PinnedGame: String { return self._s[2951]! } + public var VoiceOver_Chat_ForwardedFromYou: String { return self._s[2952]! } + public var Notifications_Badge_CountUnreadMessages_InfoOff: String { return self._s[2954]! } + public var Login_CallRequestState2: String { return self._s[2956]! } + public var CheckoutInfo_ReceiverInfoNamePlaceholder: String { return self._s[2958]! } public func VoiceOver_Chat_PhotoFrom(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2949]!, self._r[2949]!, [_0]) + return formatWithArgumentRanges(self._s[2959]!, self._r[2959]!, [_0]) } public func Checkout_PayPrice(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2951]!, self._r[2951]!, [_0]) + return formatWithArgumentRanges(self._s[2961]!, self._r[2961]!, [_0]) } - public var AuthSessions_AddDevice: String { return self._s[2952]! } - public var WallpaperPreview_Blurred: String { return self._s[2953]! } - public var Conversation_InstantPagePreview: String { return self._s[2954]! } - public var PeerInfo_ButtonUnmute: String { return self._s[2955]! } + public var AuthSessions_AddDevice: String { return self._s[2962]! } + public var WallpaperPreview_Blurred: String { return self._s[2963]! } + public var Conversation_InstantPagePreview: String { return self._s[2964]! } + public var PeerInfo_ButtonUnmute: String { return self._s[2965]! } public func DialogList_SingleUploadingVideoSuffix(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2956]!, self._r[2956]!, [_0]) + return formatWithArgumentRanges(self._s[2966]!, self._r[2966]!, [_0]) } - public var SecretTimer_VideoDescription: String { return self._s[2959]! } - public var WallpaperSearch_ColorRed: String { return self._s[2960]! } - public var GroupPermission_NoPinMessages: String { return self._s[2961]! } - public var Passport_Language_es: String { return self._s[2962]! } - public var Permissions_ContactsAllow_v0: String { return self._s[2964]! } - public var Conversation_EditingMessageMediaEditCurrentVideo: String { return self._s[2965]! } + public var SecretTimer_VideoDescription: String { return self._s[2969]! } + public var WallpaperSearch_ColorRed: String { return self._s[2970]! } + public var GroupPermission_NoPinMessages: String { return self._s[2971]! } + public var Passport_Language_es: String { return self._s[2972]! } + public var Permissions_ContactsAllow_v0: String { return self._s[2974]! } + public var Conversation_EditingMessageMediaEditCurrentVideo: String { return self._s[2975]! } public func PUSH_CHAT_MESSAGE_CONTACT(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2966]!, self._r[2966]!, [_1, _2]) + return formatWithArgumentRanges(self._s[2976]!, self._r[2976]!, [_1, _2]) } - public var Privacy_Forwards_CustomHelp: String { return self._s[2967]! } - public var WebPreview_GettingLinkInfo: String { return self._s[2968]! } - public var Watch_UserInfo_Unmute: String { return self._s[2969]! } - public var GroupInfo_ChannelListNamePlaceholder: String { return self._s[2970]! } - public var AccessDenied_CameraRestricted: String { return self._s[2972]! } + public var Privacy_Forwards_CustomHelp: String { return self._s[2977]! } + public var WebPreview_GettingLinkInfo: String { return self._s[2978]! } + public var Watch_UserInfo_Unmute: String { return self._s[2979]! } + public var GroupInfo_ChannelListNamePlaceholder: String { return self._s[2980]! } + public var AccessDenied_CameraRestricted: String { return self._s[2982]! } public func Conversation_Kilobytes(_ _0: Int) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2973]!, self._r[2973]!, ["\(_0)"]) + return formatWithArgumentRanges(self._s[2983]!, self._r[2983]!, ["\(_0)"]) } - public var ChatList_ReadAll: String { return self._s[2975]! } - public var Settings_CopyUsername: String { return self._s[2976]! } - public var Contacts_SearchLabel: String { return self._s[2977]! } - public var Map_OpenInYandexNavigator: String { return self._s[2979]! } - public var PasscodeSettings_EncryptData: String { return self._s[2980]! } - public var Settings_Wallet: String { return self._s[2981]! } - public var Group_ErrorSupergroupConversionNotPossible: String { return self._s[2982]! } - public var WallpaperSearch_ColorPrefix: String { return self._s[2983]! } - public var Notifications_GroupNotificationsPreview: String { return self._s[2984]! } - public var DialogList_AdNoticeAlert: String { return self._s[2985]! } - public var Wallet_Month_GenMay: String { return self._s[2987]! } - public var CheckoutInfo_ShippingInfoAddress1: String { return self._s[2988]! } - public var CheckoutInfo_ShippingInfoAddress2: String { return self._s[2989]! } - public var Localization_LanguageCustom: String { return self._s[2990]! } - public var Passport_Identity_TypeDriversLicenseUploadScan: String { return self._s[2991]! } - public var CallFeedback_Title: String { return self._s[2992]! } - public var VoiceOver_Chat_RecordPreviewVoiceMessage: String { return self._s[2995]! } - public var Passport_Address_OneOfTypePassportRegistration: String { return self._s[2996]! } - public var Wallet_Intro_CreateErrorTitle: String { return self._s[2997]! } - public var Conversation_InfoGroup: String { return self._s[2998]! } - public var Compose_NewMessage: String { return self._s[2999]! } - public var FastTwoStepSetup_HintPlaceholder: String { return self._s[3000]! } - public var ChatSettings_AutoDownloadVideoMessages: String { return self._s[3001]! } - public var Wallet_SecureStorageReset_BiometryFaceId: String { return self._s[3002]! } - public var Channel_DiscussionGroup_UnlinkChannel: String { return self._s[3003]! } + public var ChatList_ReadAll: String { return self._s[2985]! } + public var Settings_CopyUsername: String { return self._s[2986]! } + public var Contacts_SearchLabel: String { return self._s[2987]! } + public var Map_OpenInYandexNavigator: String { return self._s[2989]! } + public var PasscodeSettings_EncryptData: String { return self._s[2990]! } + public var Settings_Wallet: String { return self._s[2991]! } + public var Group_ErrorSupergroupConversionNotPossible: String { return self._s[2992]! } + public var WallpaperSearch_ColorPrefix: String { return self._s[2993]! } + public var Notifications_GroupNotificationsPreview: String { return self._s[2994]! } + public var DialogList_AdNoticeAlert: String { return self._s[2995]! } + public var Wallet_Month_GenMay: String { return self._s[2997]! } + public var CheckoutInfo_ShippingInfoAddress1: String { return self._s[2998]! } + public var CheckoutInfo_ShippingInfoAddress2: String { return self._s[2999]! } + public var Localization_LanguageCustom: String { return self._s[3000]! } + public var Passport_Identity_TypeDriversLicenseUploadScan: String { return self._s[3001]! } + public var CallFeedback_Title: String { return self._s[3002]! } + public var VoiceOver_Chat_RecordPreviewVoiceMessage: String { return self._s[3005]! } + public var Passport_Address_OneOfTypePassportRegistration: String { return self._s[3006]! } + public var Wallet_Intro_CreateErrorTitle: String { return self._s[3007]! } + public var Conversation_InfoGroup: String { return self._s[3008]! } + public var Compose_NewMessage: String { return self._s[3009]! } + public var FastTwoStepSetup_HintPlaceholder: String { return self._s[3010]! } + public var ChatSettings_AutoDownloadVideoMessages: String { return self._s[3011]! } + public var Wallet_SecureStorageReset_BiometryFaceId: String { return self._s[3012]! } + public var Channel_DiscussionGroup_UnlinkChannel: String { return self._s[3013]! } public func Passport_Scans_ScanIndex(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3004]!, self._r[3004]!, [_0]) + return formatWithArgumentRanges(self._s[3014]!, self._r[3014]!, [_0]) } - public var Channel_AdminLog_CanDeleteMessages: String { return self._s[3005]! } - public var Login_CancelSignUpConfirmation: String { return self._s[3006]! } - public var ChangePhoneNumberCode_Help: String { return self._s[3007]! } - public var PrivacySettings_DeleteAccountHelp: String { return self._s[3008]! } - public var Channel_BlackList_Title: String { return self._s[3009]! } - public var UserInfo_PhoneCall: String { return self._s[3010]! } - public var Passport_Address_OneOfTypeBankStatement: String { return self._s[3012]! } - public var Wallet_Month_ShortJanuary: String { return self._s[3013]! } - public var State_connecting: String { return self._s[3014]! } - public var Appearance_ThemePreview_ChatList_6_Text: String { return self._s[3015]! } - public var Wallet_Month_GenMarch: String { return self._s[3016]! } - public var EditTheme_Expand_BottomInfo: String { return self._s[3017]! } - public var AuthSessions_AddedDeviceTerminate: String { return self._s[3018]! } + public var Channel_AdminLog_CanDeleteMessages: String { return self._s[3015]! } + public var Login_CancelSignUpConfirmation: String { return self._s[3016]! } + public var ChangePhoneNumberCode_Help: String { return self._s[3017]! } + public var PrivacySettings_DeleteAccountHelp: String { return self._s[3018]! } + public var Channel_BlackList_Title: String { return self._s[3019]! } + public var UserInfo_PhoneCall: String { return self._s[3020]! } + public var Passport_Address_OneOfTypeBankStatement: String { return self._s[3022]! } + public var Wallet_Month_ShortJanuary: String { return self._s[3023]! } + public var State_connecting: String { return self._s[3024]! } + public var Appearance_ThemePreview_ChatList_6_Text: String { return self._s[3025]! } + public var Wallet_Month_GenMarch: String { return self._s[3026]! } + public var EditTheme_Expand_BottomInfo: String { return self._s[3027]! } + public var AuthSessions_AddedDeviceTerminate: String { return self._s[3028]! } public func LastSeen_TodayAt(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3019]!, self._r[3019]!, [_0]) + return formatWithArgumentRanges(self._s[3029]!, self._r[3029]!, [_0]) } public func DialogList_SingleRecordingAudioSuffix(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3020]!, self._r[3020]!, [_0]) + return formatWithArgumentRanges(self._s[3030]!, self._r[3030]!, [_0]) } - public var Notifications_GroupNotifications: String { return self._s[3021]! } - public var Conversation_SendMessageErrorTooMuchScheduled: String { return self._s[3022]! } - public var Passport_Identity_EditPassport: String { return self._s[3023]! } - public var EnterPasscode_RepeatNewPasscode: String { return self._s[3025]! } - public var Localization_EnglishLanguageName: String { return self._s[3026]! } - public var Share_AuthDescription: String { return self._s[3027]! } - public var SettingsSearch_Synonyms_Notifications_ChannelNotificationsAlert: String { return self._s[3028]! } - public var Passport_Identity_Surname: String { return self._s[3029]! } - public var Compose_TokenListPlaceholder: String { return self._s[3030]! } - public var Wallet_AccessDenied_Camera: String { return self._s[3031]! } - public var Passport_Identity_OneOfTypePassport: String { return self._s[3032]! } - public var Settings_AboutEmpty: String { return self._s[3033]! } - public var Conversation_Unmute: String { return self._s[3034]! } - public var CreateGroup_ChannelsTooMuch: String { return self._s[3036]! } - public var Wallet_Sending_Text: String { return self._s[3037]! } + public var Notifications_GroupNotifications: String { return self._s[3031]! } + public var Conversation_SendMessageErrorTooMuchScheduled: String { return self._s[3032]! } + public var Passport_Identity_EditPassport: String { return self._s[3033]! } + public var EnterPasscode_RepeatNewPasscode: String { return self._s[3035]! } + public var Localization_EnglishLanguageName: String { return self._s[3036]! } + public var Share_AuthDescription: String { return self._s[3037]! } + public var SettingsSearch_Synonyms_Notifications_ChannelNotificationsAlert: String { return self._s[3038]! } + public var Passport_Identity_Surname: String { return self._s[3039]! } + public var Compose_TokenListPlaceholder: String { return self._s[3040]! } + public var Wallet_AccessDenied_Camera: String { return self._s[3041]! } + public var Passport_Identity_OneOfTypePassport: String { return self._s[3042]! } + public var Settings_AboutEmpty: String { return self._s[3043]! } + public var Conversation_Unmute: String { return self._s[3044]! } + public var CreateGroup_ChannelsTooMuch: String { return self._s[3046]! } + public var Wallet_Sending_Text: String { return self._s[3047]! } public func PUSH_CONTACT_JOINED(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3038]!, self._r[3038]!, [_1]) + return formatWithArgumentRanges(self._s[3048]!, self._r[3048]!, [_1]) } - public var Login_CodeSentCall: String { return self._s[3039]! } - public var ContactInfo_PhoneLabelHomeFax: String { return self._s[3041]! } - public var ChatSettings_Appearance: String { return self._s[3042]! } - public var ClearCache_StorageUsage: String { return self._s[3043]! } - public var Appearance_PickAccentColor: String { return self._s[3044]! } + public var Login_CodeSentCall: String { return self._s[3049]! } + public var ContactInfo_PhoneLabelHomeFax: String { return self._s[3051]! } + public var ChatSettings_Appearance: String { return self._s[3052]! } + public var ClearCache_StorageUsage: String { return self._s[3053]! } + public var Appearance_PickAccentColor: String { return self._s[3054]! } public func PUSH_CHAT_MESSAGE_NOTEXT(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3045]!, self._r[3045]!, [_1, _2]) + return formatWithArgumentRanges(self._s[3055]!, self._r[3055]!, [_1, _2]) } public func PUSH_MESSAGE_GEO(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3046]!, self._r[3046]!, [_1]) + return formatWithArgumentRanges(self._s[3056]!, self._r[3056]!, [_1]) } - public var Notification_CallMissed: String { return self._s[3047]! } - public var SettingsSearch_Synonyms_Appearance_ChatBackground_Custom: String { return self._s[3048]! } - public var Channel_AdminLogFilter_EventsInfo: String { return self._s[3049]! } - public var Wallet_Month_GenOctober: String { return self._s[3051]! } - public var ChatAdmins_AdminLabel: String { return self._s[3052]! } - public var KeyCommand_JumpToNextChat: String { return self._s[3053]! } - public var Conversation_StopPollConfirmationTitle: String { return self._s[3055]! } - public var ChangePhoneNumberCode_CodePlaceholder: String { return self._s[3056]! } - public var Month_GenJune: String { return self._s[3057]! } - public var IntentsSettings_MainAccountInfo: String { return self._s[3058]! } - public var Watch_Location_Current: String { return self._s[3059]! } - public var Wallet_Receive_CopyInvoiceUrl: String { return self._s[3060]! } - public var Conversation_TitleMute: String { return self._s[3061]! } - public var Map_PlacesInThisArea: String { return self._s[3062]! } + public var Notification_CallMissed: String { return self._s[3057]! } + public var SettingsSearch_Synonyms_Appearance_ChatBackground_Custom: String { return self._s[3058]! } + public var Channel_AdminLogFilter_EventsInfo: String { return self._s[3059]! } + public var Wallet_Month_GenOctober: String { return self._s[3061]! } + public var ChatAdmins_AdminLabel: String { return self._s[3062]! } + public var KeyCommand_JumpToNextChat: String { return self._s[3063]! } + public var Conversation_StopPollConfirmationTitle: String { return self._s[3065]! } + public var ChangePhoneNumberCode_CodePlaceholder: String { return self._s[3066]! } + public var Month_GenJune: String { return self._s[3067]! } + public var IntentsSettings_MainAccountInfo: String { return self._s[3068]! } + public var Watch_Location_Current: String { return self._s[3069]! } + public var Wallet_Receive_CopyInvoiceUrl: String { return self._s[3070]! } + public var Conversation_TitleMute: String { return self._s[3071]! } + public var Map_PlacesInThisArea: String { return self._s[3072]! } public func PUSH_CHANNEL_MESSAGE_ROUND(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3063]!, self._r[3063]!, [_1]) + return formatWithArgumentRanges(self._s[3073]!, self._r[3073]!, [_1]) } - public var GroupInfo_DeleteAndExit: String { return self._s[3064]! } + public var GroupInfo_DeleteAndExit: String { return self._s[3074]! } public func Conversation_Moderate_DeleteAllMessages(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3065]!, self._r[3065]!, [_0]) + return formatWithArgumentRanges(self._s[3075]!, self._r[3075]!, [_0]) } - public var Call_ReportPlaceholder: String { return self._s[3066]! } - public var Chat_SlowmodeSendError: String { return self._s[3067]! } - public var MaskStickerSettings_Info: String { return self._s[3068]! } - public var EditTheme_Expand_TopInfo: String { return self._s[3069]! } + public var Call_ReportPlaceholder: String { return self._s[3076]! } + public var Chat_SlowmodeSendError: String { return self._s[3077]! } + public var MaskStickerSettings_Info: String { return self._s[3078]! } + public var EditTheme_Expand_TopInfo: String { return self._s[3079]! } public func GroupInfo_AddParticipantConfirmation(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3070]!, self._r[3070]!, [_0]) + return formatWithArgumentRanges(self._s[3080]!, self._r[3080]!, [_0]) } - public var Checkout_NewCard_PostcodeTitle: String { return self._s[3071]! } - public var Passport_Address_RegionPlaceholder: String { return self._s[3073]! } - public var Contacts_ShareTelegram: String { return self._s[3074]! } - public var EnterPasscode_EnterNewPasscodeNew: String { return self._s[3075]! } - public var Map_AddressOnMap: String { return self._s[3076]! } - public var Channel_ErrorAccessDenied: String { return self._s[3077]! } - public var UserInfo_ScamBotWarning: String { return self._s[3079]! } - public var Stickers_GroupChooseStickerPack: String { return self._s[3080]! } - public var Call_ConnectionErrorTitle: String { return self._s[3081]! } - public var UserInfo_NotificationsEnable: String { return self._s[3082]! } - public var ArchivedChats_IntroText1: String { return self._s[3083]! } - public var Tour_Text4: String { return self._s[3086]! } - public var WallpaperSearch_Recent: String { return self._s[3087]! } - public var GroupInfo_ScamGroupWarning: String { return self._s[3088]! } - public var PeopleNearby_MakeVisibleTitle: String { return self._s[3089]! } - public var Profile_MessageLifetime2s: String { return self._s[3091]! } - public var Appearance_ThemePreview_ChatList_5_Text: String { return self._s[3092]! } - public var Notification_MessageLifetime2s: String { return self._s[3093]! } + public var Checkout_NewCard_PostcodeTitle: String { return self._s[3081]! } + public var Passport_Address_RegionPlaceholder: String { return self._s[3083]! } + public var Contacts_ShareTelegram: String { return self._s[3084]! } + public var EnterPasscode_EnterNewPasscodeNew: String { return self._s[3085]! } + public var Map_AddressOnMap: String { return self._s[3086]! } + public var Channel_ErrorAccessDenied: String { return self._s[3087]! } + public var UserInfo_ScamBotWarning: String { return self._s[3089]! } + public var Stickers_GroupChooseStickerPack: String { return self._s[3090]! } + public var Call_ConnectionErrorTitle: String { return self._s[3091]! } + public var UserInfo_NotificationsEnable: String { return self._s[3092]! } + public var ArchivedChats_IntroText1: String { return self._s[3093]! } + public var Tour_Text4: String { return self._s[3096]! } + public var WallpaperSearch_Recent: String { return self._s[3097]! } + public var GroupInfo_ScamGroupWarning: String { return self._s[3098]! } + public var PeopleNearby_MakeVisibleTitle: String { return self._s[3099]! } + public var Profile_MessageLifetime2s: String { return self._s[3101]! } + public var Appearance_ThemePreview_ChatList_5_Text: String { return self._s[3102]! } + public var Notification_MessageLifetime2s: String { return self._s[3103]! } public func Time_PreciseDate_m10(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3094]!, self._r[3094]!, [_1, _2, _3]) + return formatWithArgumentRanges(self._s[3104]!, self._r[3104]!, [_1, _2, _3]) } - public var Cache_ClearCache: String { return self._s[3095]! } - public var AutoNightTheme_UpdateLocation: String { return self._s[3096]! } - public var Permissions_NotificationsUnreachableText_v0: String { return self._s[3097]! } + public var Cache_ClearCache: String { return self._s[3105]! } + public var AutoNightTheme_UpdateLocation: String { return self._s[3106]! } + public var Permissions_NotificationsUnreachableText_v0: String { return self._s[3107]! } public func Channel_AdminLog_MessageChangedGroupUsername(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3101]!, self._r[3101]!, [_0]) + return formatWithArgumentRanges(self._s[3111]!, self._r[3111]!, [_0]) } public func Conversation_ShareMyPhoneNumber_StatusSuccess(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3103]!, self._r[3103]!, [_0]) + return formatWithArgumentRanges(self._s[3113]!, self._r[3113]!, [_0]) } - public var LocalGroup_Text: String { return self._s[3104]! } - public var PeerInfo_PaneMembers: String { return self._s[3105]! } - public var Channel_AdminLog_EmptyFilterTitle: String { return self._s[3106]! } - public var SocksProxySetup_TypeSocks: String { return self._s[3107]! } - public var ChatList_UnarchiveAction: String { return self._s[3108]! } - public var AutoNightTheme_Title: String { return self._s[3109]! } - public var InstantPage_FeedbackButton: String { return self._s[3110]! } - public var Passport_FieldAddress: String { return self._s[3111]! } + public var LocalGroup_Text: String { return self._s[3114]! } + public var PeerInfo_PaneMembers: String { return self._s[3115]! } + public var Channel_AdminLog_EmptyFilterTitle: String { return self._s[3116]! } + public var SocksProxySetup_TypeSocks: String { return self._s[3117]! } + public var ChatList_UnarchiveAction: String { return self._s[3118]! } + public var AutoNightTheme_Title: String { return self._s[3119]! } + public var InstantPage_FeedbackButton: String { return self._s[3120]! } + public var Passport_FieldAddress: String { return self._s[3121]! } public func Channel_AdminLog_SetSlowmode(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3112]!, self._r[3112]!, [_1, _2]) + return formatWithArgumentRanges(self._s[3122]!, self._r[3122]!, [_1, _2]) } - public var Month_ShortMarch: String { return self._s[3113]! } + public var Month_ShortMarch: String { return self._s[3123]! } public func PUSH_MESSAGE_INVOICE(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3114]!, self._r[3114]!, [_1, _2]) + return formatWithArgumentRanges(self._s[3124]!, self._r[3124]!, [_1, _2]) } - public var SocksProxySetup_UsernamePlaceholder: String { return self._s[3115]! } - public var Conversation_ShareInlineBotLocationConfirmation: String { return self._s[3116]! } - public var Passport_FloodError: String { return self._s[3117]! } - public var SecretGif_Title: String { return self._s[3118]! } - public var NotificationSettings_ShowNotificationsAllAccountsInfoOn: String { return self._s[3119]! } - public var ChatList_Context_UnhideArchive: String { return self._s[3120]! } - public var Passport_Language_th: String { return self._s[3122]! } - public var Passport_Address_Address: String { return self._s[3123]! } - public var Login_InvalidLastNameError: String { return self._s[3124]! } - public var Notifications_InAppNotificationsPreview: String { return self._s[3125]! } - public var Notifications_PermissionsUnreachableTitle: String { return self._s[3126]! } - public var ChatList_Context_Archive: String { return self._s[3127]! } - public var SettingsSearch_FAQ: String { return self._s[3128]! } - public var ShareMenu_Send: String { return self._s[3129]! } - public var ChatState_Connecting: String { return self._s[3130]! } - public var WallpaperSearch_ColorYellow: String { return self._s[3132]! } - public var Month_GenNovember: String { return self._s[3134]! } - public var SettingsSearch_Synonyms_Appearance_LargeEmoji: String { return self._s[3136]! } + public var SocksProxySetup_UsernamePlaceholder: String { return self._s[3125]! } + public var Conversation_ShareInlineBotLocationConfirmation: String { return self._s[3126]! } + public var Passport_FloodError: String { return self._s[3127]! } + public var SecretGif_Title: String { return self._s[3128]! } + public var NotificationSettings_ShowNotificationsAllAccountsInfoOn: String { return self._s[3129]! } + public var ChatList_Context_UnhideArchive: String { return self._s[3130]! } + public var Passport_Language_th: String { return self._s[3132]! } + public var Passport_Address_Address: String { return self._s[3133]! } + public var Login_InvalidLastNameError: String { return self._s[3134]! } + public var Notifications_InAppNotificationsPreview: String { return self._s[3135]! } + public var Notifications_PermissionsUnreachableTitle: String { return self._s[3136]! } + public var ChatList_Context_Archive: String { return self._s[3137]! } + public var SettingsSearch_FAQ: String { return self._s[3138]! } + public var ShareMenu_Send: String { return self._s[3139]! } + public var ChatState_Connecting: String { return self._s[3140]! } + public var WallpaperSearch_ColorYellow: String { return self._s[3142]! } + public var Month_GenNovember: String { return self._s[3144]! } + public var SettingsSearch_Synonyms_Appearance_LargeEmoji: String { return self._s[3146]! } public func Conversation_ShareMyPhoneNumberConfirmation(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3137]!, self._r[3137]!, [_1, _2]) + return formatWithArgumentRanges(self._s[3147]!, self._r[3147]!, [_1, _2]) } - public var Conversation_SwipeToReplyHintText: String { return self._s[3138]! } - public var Checkout_Email: String { return self._s[3139]! } - public var NotificationsSound_Tritone: String { return self._s[3140]! } - public var StickerPacksSettings_ManagingHelp: String { return self._s[3142]! } - public var Wallet_ContextMenuCopy: String { return self._s[3144]! } + public var Conversation_SwipeToReplyHintText: String { return self._s[3148]! } + public var Checkout_Email: String { return self._s[3149]! } + public var NotificationsSound_Tritone: String { return self._s[3150]! } + public var StickerPacksSettings_ManagingHelp: String { return self._s[3152]! } + public var Wallet_ContextMenuCopy: String { return self._s[3154]! } public func Wallet_Time_PreciseDate_m6(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3146]!, self._r[3146]!, [_1, _2, _3]) + return formatWithArgumentRanges(self._s[3156]!, self._r[3156]!, [_1, _2, _3]) } - public var Appearance_TextSize_Automatic: String { return self._s[3147]! } + public var Appearance_TextSize_Automatic: String { return self._s[3157]! } public func PUSH_PINNED_ROUND(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3148]!, self._r[3148]!, [_1]) + return formatWithArgumentRanges(self._s[3158]!, self._r[3158]!, [_1]) } public func StickerPackActionInfo_AddedText(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3149]!, self._r[3149]!, [_0]) + return formatWithArgumentRanges(self._s[3159]!, self._r[3159]!, [_0]) } - public var ChangePhoneNumberNumber_Help: String { return self._s[3150]! } + public var ChangePhoneNumberNumber_Help: String { return self._s[3160]! } public func Checkout_LiabilityAlert(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3151]!, self._r[3151]!, [_1, _1, _1, _2]) + return formatWithArgumentRanges(self._s[3161]!, self._r[3161]!, [_1, _1, _1, _2]) } - public var ChatList_UndoArchiveTitle: String { return self._s[3152]! } - public var Notification_Exceptions_Add: String { return self._s[3153]! } - public var DialogList_You: String { return self._s[3154]! } - public var MediaPicker_Send: String { return self._s[3157]! } - public var SettingsSearch_Synonyms_Stickers_Title: String { return self._s[3158]! } - public var Appearance_ThemePreview_ChatList_4_Text: String { return self._s[3159]! } - public var Call_AudioRouteSpeaker: String { return self._s[3160]! } - public var Watch_UserInfo_Title: String { return self._s[3161]! } - public var VoiceOver_Chat_PollFinalResults: String { return self._s[3162]! } - public var Appearance_AccentColor: String { return self._s[3164]! } + public var ChatList_UndoArchiveTitle: String { return self._s[3162]! } + public var Notification_Exceptions_Add: String { return self._s[3163]! } + public var DialogList_You: String { return self._s[3164]! } + public var MediaPicker_Send: String { return self._s[3167]! } + public var SettingsSearch_Synonyms_Stickers_Title: String { return self._s[3168]! } + public var Appearance_ThemePreview_ChatList_4_Text: String { return self._s[3169]! } + public var Call_AudioRouteSpeaker: String { return self._s[3170]! } + public var Watch_UserInfo_Title: String { return self._s[3171]! } + public var VoiceOver_Chat_PollFinalResults: String { return self._s[3172]! } + public var Appearance_AccentColor: String { return self._s[3174]! } public func Login_EmailPhoneSubject(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3165]!, self._r[3165]!, [_0]) + return formatWithArgumentRanges(self._s[3175]!, self._r[3175]!, [_0]) } - public var Permissions_ContactsAllowInSettings_v0: String { return self._s[3166]! } + public var Permissions_ContactsAllowInSettings_v0: String { return self._s[3176]! } public func PUSH_CHANNEL_MESSAGE_GAME(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3167]!, self._r[3167]!, [_1, _2]) + return formatWithArgumentRanges(self._s[3177]!, self._r[3177]!, [_1, _2]) } - public var Conversation_ClousStorageInfo_Description2: String { return self._s[3168]! } - public var WebSearch_RecentClearConfirmation: String { return self._s[3169]! } - public var Notification_CallOutgoing: String { return self._s[3170]! } - public var PrivacySettings_PasscodeAndFaceId: String { return self._s[3171]! } - public var Channel_DiscussionGroup_MakeHistoryPublic: String { return self._s[3172]! } - public var Call_RecordingDisabledMessage: String { return self._s[3173]! } - public var Message_Game: String { return self._s[3174]! } - public var Conversation_PressVolumeButtonForSound: String { return self._s[3175]! } - public var PrivacyLastSeenSettings_CustomHelp: String { return self._s[3176]! } - public var Channel_DiscussionGroup_PrivateGroup: String { return self._s[3177]! } - public var Channel_EditAdmin_PermissionAddAdmins: String { return self._s[3178]! } - public var Date_DialogDateFormat: String { return self._s[3180]! } - public var WallpaperColors_SetCustomColor: String { return self._s[3181]! } - public var Notifications_InAppNotifications: String { return self._s[3182]! } + public var Conversation_ClousStorageInfo_Description2: String { return self._s[3178]! } + public var WebSearch_RecentClearConfirmation: String { return self._s[3179]! } + public var Notification_CallOutgoing: String { return self._s[3180]! } + public var PrivacySettings_PasscodeAndFaceId: String { return self._s[3181]! } + public var Channel_DiscussionGroup_MakeHistoryPublic: String { return self._s[3182]! } + public var Call_RecordingDisabledMessage: String { return self._s[3183]! } + public var Message_Game: String { return self._s[3184]! } + public var Conversation_PressVolumeButtonForSound: String { return self._s[3185]! } + public var PrivacyLastSeenSettings_CustomHelp: String { return self._s[3186]! } + public var Channel_DiscussionGroup_PrivateGroup: String { return self._s[3187]! } + public var Channel_EditAdmin_PermissionAddAdmins: String { return self._s[3188]! } + public var Date_DialogDateFormat: String { return self._s[3190]! } + public var WallpaperColors_SetCustomColor: String { return self._s[3191]! } + public var Notifications_InAppNotifications: String { return self._s[3192]! } public func Channel_Management_RemovedBy(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3183]!, self._r[3183]!, [_0]) + return formatWithArgumentRanges(self._s[3193]!, self._r[3193]!, [_0]) } public func Settings_ApplyProxyAlert(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3184]!, self._r[3184]!, [_1, _2]) + return formatWithArgumentRanges(self._s[3194]!, self._r[3194]!, [_1, _2]) } - public var NewContact_Title: String { return self._s[3185]! } + public var NewContact_Title: String { return self._s[3195]! } public func AutoDownloadSettings_UpToForAll(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3186]!, self._r[3186]!, [_0]) + return formatWithArgumentRanges(self._s[3196]!, self._r[3196]!, [_0]) } - public var Conversation_ViewContactDetails: String { return self._s[3187]! } + public var Conversation_ViewContactDetails: String { return self._s[3197]! } public func PUSH_CHANNEL_MESSAGE_CONTACT(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3189]!, self._r[3189]!, [_1]) + return formatWithArgumentRanges(self._s[3199]!, self._r[3199]!, [_1]) } - public var Checkout_NewCard_CardholderNameTitle: String { return self._s[3190]! } - public var Passport_Identity_ExpiryDateNone: String { return self._s[3191]! } - public var PrivacySettings_Title: String { return self._s[3192]! } - public var Conversation_SilentBroadcastTooltipOff: String { return self._s[3195]! } - public var GroupRemoved_UsersSectionTitle: String { return self._s[3196]! } - public var VoiceOver_Chat_ContactEmail: String { return self._s[3197]! } - public var Contacts_PhoneNumber: String { return self._s[3198]! } - public var PeerInfo_ButtonMute: String { return self._s[3199]! } - public var TwoFactorSetup_Password_PlaceholderConfirmPassword: String { return self._s[3201]! } - public var Map_ShowPlaces: String { return self._s[3202]! } - public var ChatAdmins_Title: String { return self._s[3203]! } - public var InstantPage_Reference: String { return self._s[3205]! } - public var Wallet_Info_Updating: String { return self._s[3206]! } - public var ReportGroupLocation_Text: String { return self._s[3207]! } + public var Checkout_NewCard_CardholderNameTitle: String { return self._s[3200]! } + public var Passport_Identity_ExpiryDateNone: String { return self._s[3201]! } + public var PrivacySettings_Title: String { return self._s[3202]! } + public var Conversation_SilentBroadcastTooltipOff: String { return self._s[3205]! } + public var GroupRemoved_UsersSectionTitle: String { return self._s[3206]! } + public var VoiceOver_Chat_ContactEmail: String { return self._s[3207]! } + public var Contacts_PhoneNumber: String { return self._s[3208]! } + public var PeerInfo_ButtonMute: String { return self._s[3209]! } + public var TwoFactorSetup_Password_PlaceholderConfirmPassword: String { return self._s[3211]! } + public var Map_ShowPlaces: String { return self._s[3212]! } + public var ChatAdmins_Title: String { return self._s[3213]! } + public var InstantPage_Reference: String { return self._s[3215]! } + public var Wallet_Info_Updating: String { return self._s[3216]! } + public var ReportGroupLocation_Text: String { return self._s[3217]! } public func PUSH_CHAT_MESSAGE_FWD(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3208]!, self._r[3208]!, [_1, _2]) + return formatWithArgumentRanges(self._s[3218]!, self._r[3218]!, [_1, _2]) } - public var Camera_FlashOff: String { return self._s[3209]! } - public var Watch_UserInfo_Block: String { return self._s[3210]! } - public var ChatSettings_Stickers: String { return self._s[3211]! } - public var ChatSettings_DownloadInBackground: String { return self._s[3212]! } - public var Appearance_ThemeCarouselTintedNight: String { return self._s[3213]! } + public var Camera_FlashOff: String { return self._s[3219]! } + public var Watch_UserInfo_Block: String { return self._s[3220]! } + public var ChatSettings_Stickers: String { return self._s[3221]! } + public var ChatSettings_DownloadInBackground: String { return self._s[3222]! } + public var Appearance_ThemeCarouselTintedNight: String { return self._s[3223]! } public func UserInfo_BlockConfirmation(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3214]!, self._r[3214]!, [_0]) + return formatWithArgumentRanges(self._s[3224]!, self._r[3224]!, [_0]) } - public var Settings_ViewPhoto: String { return self._s[3215]! } - public var Login_CheckOtherSessionMessages: String { return self._s[3216]! } - public var AutoDownloadSettings_Cellular: String { return self._s[3217]! } - public var Wallet_Created_ExportErrorTitle: String { return self._s[3218]! } - public var SettingsSearch_Synonyms_Notifications_GroupNotificationsExceptions: String { return self._s[3219]! } - public var VoiceOver_MessageContextShare: String { return self._s[3220]! } + public var Settings_ViewPhoto: String { return self._s[3225]! } + public var Login_CheckOtherSessionMessages: String { return self._s[3226]! } + public var AutoDownloadSettings_Cellular: String { return self._s[3227]! } + public var Wallet_Created_ExportErrorTitle: String { return self._s[3228]! } + public var SettingsSearch_Synonyms_Notifications_GroupNotificationsExceptions: String { return self._s[3229]! } + public var VoiceOver_MessageContextShare: String { return self._s[3230]! } public func Target_InviteToGroupConfirmation(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3222]!, self._r[3222]!, [_0]) + return formatWithArgumentRanges(self._s[3232]!, self._r[3232]!, [_0]) } - public var Privacy_DeleteDrafts: String { return self._s[3223]! } - public var Wallpaper_SetCustomBackgroundInfo: String { return self._s[3224]! } + public var Privacy_DeleteDrafts: String { return self._s[3233]! } + public var Wallpaper_SetCustomBackgroundInfo: String { return self._s[3234]! } public func LastSeen_AtDate(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3225]!, self._r[3225]!, [_0]) + return formatWithArgumentRanges(self._s[3235]!, self._r[3235]!, [_0]) } - public var DialogList_SavedMessagesHelp: String { return self._s[3226]! } - public var Wallet_SecureStorageNotAvailable_Title: String { return self._s[3227]! } - public var DialogList_SavedMessages: String { return self._s[3228]! } - public var GroupInfo_UpgradeButton: String { return self._s[3229]! } - public var Appearance_ThemePreview_ChatList_3_Text: String { return self._s[3231]! } - public var DialogList_Pin: String { return self._s[3232]! } + public var DialogList_SavedMessagesHelp: String { return self._s[3236]! } + public var Wallet_SecureStorageNotAvailable_Title: String { return self._s[3237]! } + public var DialogList_SavedMessages: String { return self._s[3238]! } + public var GroupInfo_UpgradeButton: String { return self._s[3239]! } + public var Appearance_ThemePreview_ChatList_3_Text: String { return self._s[3241]! } + public var DialogList_Pin: String { return self._s[3242]! } public func ForwardedAuthors2(_ _0: String, _ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3233]!, self._r[3233]!, [_0, _1]) + return formatWithArgumentRanges(self._s[3243]!, self._r[3243]!, [_0, _1]) } public func Login_PhoneGenericEmailSubject(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3234]!, self._r[3234]!, [_0]) + return formatWithArgumentRanges(self._s[3244]!, self._r[3244]!, [_0]) } - public var Notification_Exceptions_AlwaysOn: String { return self._s[3235]! } - public var UserInfo_NotificationsDisable: String { return self._s[3236]! } - public var Conversation_ContextMenuCancelEditing: String { return self._s[3237]! } - public var Paint_Outlined: String { return self._s[3238]! } - public var Activity_PlayingGame: String { return self._s[3239]! } - public var SearchImages_NoImagesFound: String { return self._s[3240]! } - public var SocksProxySetup_ProxyType: String { return self._s[3241]! } - public var AppleWatch_ReplyPresetsHelp: String { return self._s[3243]! } - public var Conversation_ContextMenuCancelSending: String { return self._s[3244]! } - public var Settings_AppLanguage: String { return self._s[3245]! } - public var TwoStepAuth_ResetAccountHelp: String { return self._s[3246]! } - public var Common_ChoosePhoto: String { return self._s[3247]! } - public var AuthSessions_AddDevice_InvalidQRCode: String { return self._s[3248]! } - public var CallFeedback_ReasonEcho: String { return self._s[3249]! } + public var Notification_Exceptions_AlwaysOn: String { return self._s[3245]! } + public var UserInfo_NotificationsDisable: String { return self._s[3246]! } + public var Conversation_ContextMenuCancelEditing: String { return self._s[3247]! } + public var Paint_Outlined: String { return self._s[3248]! } + public var Activity_PlayingGame: String { return self._s[3249]! } + public var SearchImages_NoImagesFound: String { return self._s[3250]! } + public var SocksProxySetup_ProxyType: String { return self._s[3251]! } + public var AppleWatch_ReplyPresetsHelp: String { return self._s[3253]! } + public var Conversation_ContextMenuCancelSending: String { return self._s[3254]! } + public var Settings_AppLanguage: String { return self._s[3255]! } + public var TwoStepAuth_ResetAccountHelp: String { return self._s[3256]! } + public var Common_ChoosePhoto: String { return self._s[3257]! } + public var AuthSessions_AddDevice_InvalidQRCode: String { return self._s[3258]! } + public var CallFeedback_ReasonEcho: String { return self._s[3259]! } public func PUSH_PINNED_AUDIO(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3250]!, self._r[3250]!, [_1]) + return formatWithArgumentRanges(self._s[3260]!, self._r[3260]!, [_1]) } - public var Privacy_Calls_AlwaysAllow: String { return self._s[3251]! } - public var PollResults_Collapse: String { return self._s[3252]! } - public var Activity_UploadingVideo: String { return self._s[3253]! } - public var Conversation_WalletRequiredNotNow: String { return self._s[3254]! } - public var ChannelInfo_DeleteChannelConfirmation: String { return self._s[3255]! } - public var NetworkUsageSettings_Wifi: String { return self._s[3256]! } - public var VoiceOver_Editing_ClearText: String { return self._s[3257]! } - public var PUSH_SENDER_YOU: String { return self._s[3258]! } - public var Channel_BanUser_PermissionReadMessages: String { return self._s[3259]! } - public var Checkout_PayWithTouchId: String { return self._s[3260]! } - public var Wallpaper_ResetWallpapersConfirmation: String { return self._s[3261]! } + public var Privacy_Calls_AlwaysAllow: String { return self._s[3261]! } + public var PollResults_Collapse: String { return self._s[3262]! } + public var Activity_UploadingVideo: String { return self._s[3263]! } + public var Conversation_WalletRequiredNotNow: String { return self._s[3264]! } + public var ChannelInfo_DeleteChannelConfirmation: String { return self._s[3265]! } + public var NetworkUsageSettings_Wifi: String { return self._s[3266]! } + public var VoiceOver_Editing_ClearText: String { return self._s[3267]! } + public var PUSH_SENDER_YOU: String { return self._s[3268]! } + public var Channel_BanUser_PermissionReadMessages: String { return self._s[3269]! } + public var Checkout_PayWithTouchId: String { return self._s[3270]! } + public var Wallpaper_ResetWallpapersConfirmation: String { return self._s[3271]! } public func PUSH_LOCKED_MESSAGE(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3263]!, self._r[3263]!, [_1]) + return formatWithArgumentRanges(self._s[3273]!, self._r[3273]!, [_1]) } - public var Notifications_ExceptionsNone: String { return self._s[3264]! } + public var Notifications_ExceptionsNone: String { return self._s[3274]! } public func Message_ForwardedMessageShort(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3265]!, self._r[3265]!, [_0]) + return formatWithArgumentRanges(self._s[3275]!, self._r[3275]!, [_0]) } public func PUSH_PINNED_GEO(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3266]!, self._r[3266]!, [_1]) + return formatWithArgumentRanges(self._s[3276]!, self._r[3276]!, [_1]) } - public var AuthSessions_IncompleteAttempts: String { return self._s[3268]! } - public var Passport_Address_Region: String { return self._s[3271]! } - public var ChatList_DeleteChat: String { return self._s[3272]! } - public var LogoutOptions_ClearCacheTitle: String { return self._s[3273]! } - public var PhotoEditor_TiltShift: String { return self._s[3274]! } - public var Settings_FAQ_URL: String { return self._s[3275]! } - public var TwoFactorSetup_EmailVerification_ChangeAction: String { return self._s[3276]! } - public var Passport_Language_sl: String { return self._s[3277]! } - public var Settings_PrivacySettings: String { return self._s[3279]! } - public var SharedMedia_TitleLink: String { return self._s[3280]! } - public var Passport_Identity_TypePassportUploadScan: String { return self._s[3281]! } - public var Settings_SetProfilePhoto: String { return self._s[3282]! } - public var Channel_About_Help: String { return self._s[3283]! } - public var Contacts_PermissionsEnable: String { return self._s[3284]! } - public var Wallet_Sending_Title: String { return self._s[3285]! } - public var PeerInfo_PaneMedia: String { return self._s[3286]! } - public var SettingsSearch_Synonyms_Notifications_GroupNotificationsAlert: String { return self._s[3287]! } - public var AttachmentMenu_SendAsFiles: String { return self._s[3288]! } - public var CallFeedback_ReasonInterruption: String { return self._s[3290]! } - public var Passport_Address_AddTemporaryRegistration: String { return self._s[3291]! } - public var AutoDownloadSettings_AutodownloadVideos: String { return self._s[3292]! } - public var ChatSettings_AutoDownloadSettings_Delimeter: String { return self._s[3293]! } - public var OldChannels_Title: String { return self._s[3294]! } - public var PrivacySettings_DeleteAccountTitle: String { return self._s[3295]! } - public var AccessDenied_VideoMessageCamera: String { return self._s[3297]! } - public var Map_OpenInYandexMaps: String { return self._s[3299]! } - public var CreateGroup_ErrorLocatedGroupsTooMuch: String { return self._s[3300]! } - public var VoiceOver_MessageContextReply: String { return self._s[3301]! } - public var PhotoEditor_SaturationTool: String { return self._s[3303]! } + public var AuthSessions_IncompleteAttempts: String { return self._s[3278]! } + public var Passport_Address_Region: String { return self._s[3281]! } + public var ChatList_DeleteChat: String { return self._s[3282]! } + public var LogoutOptions_ClearCacheTitle: String { return self._s[3283]! } + public var PhotoEditor_TiltShift: String { return self._s[3284]! } + public var Settings_FAQ_URL: String { return self._s[3285]! } + public var TwoFactorSetup_EmailVerification_ChangeAction: String { return self._s[3286]! } + public var Passport_Language_sl: String { return self._s[3287]! } + public var Settings_PrivacySettings: String { return self._s[3289]! } + public var SharedMedia_TitleLink: String { return self._s[3290]! } + public var Passport_Identity_TypePassportUploadScan: String { return self._s[3291]! } + public var Settings_SetProfilePhoto: String { return self._s[3292]! } + public var Channel_About_Help: String { return self._s[3293]! } + public var Contacts_PermissionsEnable: String { return self._s[3294]! } + public var Wallet_Sending_Title: String { return self._s[3295]! } + public var PeerInfo_PaneMedia: String { return self._s[3296]! } + public var SettingsSearch_Synonyms_Notifications_GroupNotificationsAlert: String { return self._s[3297]! } + public var AttachmentMenu_SendAsFiles: String { return self._s[3298]! } + public var CallFeedback_ReasonInterruption: String { return self._s[3300]! } + public var Passport_Address_AddTemporaryRegistration: String { return self._s[3301]! } + public var AutoDownloadSettings_AutodownloadVideos: String { return self._s[3302]! } + public var ChatSettings_AutoDownloadSettings_Delimeter: String { return self._s[3303]! } + public var OldChannels_Title: String { return self._s[3304]! } + public var PrivacySettings_DeleteAccountTitle: String { return self._s[3305]! } + public var AccessDenied_VideoMessageCamera: String { return self._s[3307]! } + public var Map_OpenInYandexMaps: String { return self._s[3309]! } + public var CreateGroup_ErrorLocatedGroupsTooMuch: String { return self._s[3310]! } + public var VoiceOver_MessageContextReply: String { return self._s[3311]! } + public var PhotoEditor_SaturationTool: String { return self._s[3313]! } public func PUSH_MESSAGE_STICKER(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3304]!, self._r[3304]!, [_1, _2]) + return formatWithArgumentRanges(self._s[3314]!, self._r[3314]!, [_1, _2]) } - public var PrivacyPhoneNumberSettings_CustomHelp: String { return self._s[3305]! } - public var Notification_Exceptions_NewException_NotificationHeader: String { return self._s[3306]! } - public var Group_OwnershipTransfer_ErrorLocatedGroupsTooMuch: String { return self._s[3307]! } + public var PrivacyPhoneNumberSettings_CustomHelp: String { return self._s[3315]! } + public var Notification_Exceptions_NewException_NotificationHeader: String { return self._s[3316]! } + public var Group_OwnershipTransfer_ErrorLocatedGroupsTooMuch: String { return self._s[3317]! } public func LOCAL_MESSAGE_FWDS(_ _1: String, _ _2: Int) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3308]!, self._r[3308]!, [_1, "\(_2)"]) + return formatWithArgumentRanges(self._s[3318]!, self._r[3318]!, [_1, "\(_2)"]) } - public var Appearance_ThemePreview_ChatList_2_Text: String { return self._s[3309]! } - public var Channel_Username_InvalidTooShort: String { return self._s[3311]! } - public var SettingsSearch_Synonyms_Wallet: String { return self._s[3312]! } + public var Appearance_ThemePreview_ChatList_2_Text: String { return self._s[3319]! } + public var Channel_Username_InvalidTooShort: String { return self._s[3321]! } + public var SettingsSearch_Synonyms_Wallet: String { return self._s[3322]! } public func Group_OwnershipTransfer_DescriptionInfo(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3313]!, self._r[3313]!, [_1, _2]) + return formatWithArgumentRanges(self._s[3323]!, self._r[3323]!, [_1, _2]) } - public var Forward_ErrorPublicPollDisabledInChannels: String { return self._s[3314]! } + public var Forward_ErrorPublicPollDisabledInChannels: String { return self._s[3324]! } public func PUSH_CHAT_MESSAGE_GAME(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3315]!, self._r[3315]!, [_1, _2, _3]) + return formatWithArgumentRanges(self._s[3325]!, self._r[3325]!, [_1, _2, _3]) } - public var WallpaperPreview_PatternTitle: String { return self._s[3316]! } - public var GroupInfo_PublicLinkAdd: String { return self._s[3317]! } - public var Passport_PassportInformation: String { return self._s[3320]! } - public var Theme_Unsupported: String { return self._s[3321]! } - public var WatchRemote_AlertTitle: String { return self._s[3322]! } - public var Privacy_GroupsAndChannels_NeverAllow: String { return self._s[3323]! } - public var ConvertToSupergroup_HelpText: String { return self._s[3325]! } + public var WallpaperPreview_PatternTitle: String { return self._s[3326]! } + public var GroupInfo_PublicLinkAdd: String { return self._s[3327]! } + public var Passport_PassportInformation: String { return self._s[3330]! } + public var Theme_Unsupported: String { return self._s[3331]! } + public var WatchRemote_AlertTitle: String { return self._s[3332]! } + public var Privacy_GroupsAndChannels_NeverAllow: String { return self._s[3333]! } + public var ConvertToSupergroup_HelpText: String { return self._s[3335]! } public func Time_MonthOfYear_m7(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3326]!, self._r[3326]!, [_0]) + return formatWithArgumentRanges(self._s[3336]!, self._r[3336]!, [_0]) } public func PUSH_PHONE_CALL_REQUEST(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3327]!, self._r[3327]!, [_1]) + return formatWithArgumentRanges(self._s[3337]!, self._r[3337]!, [_1]) } - public var Privacy_GroupsAndChannels_CustomHelp: String { return self._s[3328]! } - public var Wallet_Navigation_Done: String { return self._s[3330]! } - public var TwoStepAuth_RecoveryCodeInvalid: String { return self._s[3331]! } - public var AccessDenied_CameraDisabled: String { return self._s[3332]! } + public var Privacy_GroupsAndChannels_CustomHelp: String { return self._s[3338]! } + public var Wallet_Navigation_Done: String { return self._s[3340]! } + public var TwoStepAuth_RecoveryCodeInvalid: String { return self._s[3341]! } + public var AccessDenied_CameraDisabled: String { return self._s[3342]! } public func Channel_Username_UsernameIsAvailable(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3333]!, self._r[3333]!, [_0]) - } - public var ClearCache_Forever: String { return self._s[3334]! } - public var AuthSessions_AddDeviceIntro_Title: String { return self._s[3335]! } - public var CreatePoll_Quiz: String { return self._s[3336]! } - public var PhotoEditor_ContrastTool: String { return self._s[3339]! } - public func PUSH_PINNED_DOC(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3340]!, self._r[3340]!, [_1]) - } - public var DialogList_Draft: String { return self._s[3341]! } - public var Wallet_Configuration_BlockchainIdInfo: String { return self._s[3342]! } - public func PeopleNearby_VisibleUntil(_ _0: String) -> (String, [(Int, NSRange)]) { return formatWithArgumentRanges(self._s[3343]!, self._r[3343]!, [_0]) } - public var Privacy_TopPeersDelete: String { return self._s[3345]! } - public var LoginPassword_PasswordPlaceholder: String { return self._s[3346]! } - public var Passport_Identity_TypeIdentityCardUploadScan: String { return self._s[3347]! } - public var WebSearch_RecentSectionClear: String { return self._s[3348]! } - public var EditTheme_ErrorInvalidCharacters: String { return self._s[3349]! } - public var Watch_ChatList_NoConversationsTitle: String { return self._s[3351]! } - public var PeerInfo_ButtonMore: String { return self._s[3353]! } - public var Common_Done: String { return self._s[3354]! } - public var Shortcut_SwitchAccount: String { return self._s[3355]! } - public var AuthSessions_EmptyText: String { return self._s[3356]! } - public var Wallet_Configuration_BlockchainNameChangedTitle: String { return self._s[3357]! } - public var Conversation_ShareBotContactConfirmation: String { return self._s[3358]! } - public var Tour_Title5: String { return self._s[3359]! } - public var Wallet_Settings_Title: String { return self._s[3360]! } + public var ClearCache_Forever: String { return self._s[3344]! } + public var AuthSessions_AddDeviceIntro_Title: String { return self._s[3345]! } + public var CreatePoll_Quiz: String { return self._s[3346]! } + public var PhotoEditor_ContrastTool: String { return self._s[3349]! } + public func PUSH_PINNED_DOC(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3350]!, self._r[3350]!, [_1]) + } + public var DialogList_Draft: String { return self._s[3351]! } + public var Wallet_Configuration_BlockchainIdInfo: String { return self._s[3352]! } + public func PeopleNearby_VisibleUntil(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3353]!, self._r[3353]!, [_0]) + } + public var Privacy_TopPeersDelete: String { return self._s[3355]! } + public var LoginPassword_PasswordPlaceholder: String { return self._s[3356]! } + public var Passport_Identity_TypeIdentityCardUploadScan: String { return self._s[3357]! } + public var WebSearch_RecentSectionClear: String { return self._s[3358]! } + public var EditTheme_ErrorInvalidCharacters: String { return self._s[3359]! } + public var Watch_ChatList_NoConversationsTitle: String { return self._s[3361]! } + public var PeerInfo_ButtonMore: String { return self._s[3363]! } + public var Common_Done: String { return self._s[3364]! } + public var Shortcut_SwitchAccount: String { return self._s[3365]! } + public var AuthSessions_EmptyText: String { return self._s[3366]! } + public var Wallet_Configuration_BlockchainNameChangedTitle: String { return self._s[3367]! } + public var Conversation_ShareBotContactConfirmation: String { return self._s[3368]! } + public var Tour_Title5: String { return self._s[3369]! } + public var Wallet_Settings_Title: String { return self._s[3370]! } public func Map_DirectionsDriveEta(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3361]!, self._r[3361]!, [_0]) + return formatWithArgumentRanges(self._s[3371]!, self._r[3371]!, [_0]) } - public var ApplyLanguage_UnsufficientDataTitle: String { return self._s[3362]! } - public var Conversation_LinkDialogSave: String { return self._s[3363]! } - public var GroupInfo_ActionRestrict: String { return self._s[3364]! } - public var Checkout_Title: String { return self._s[3365]! } - public var Channel_DiscussionGroup_HeaderLabel: String { return self._s[3367]! } - public var Channel_AdminLog_CanChangeInfo: String { return self._s[3369]! } - public var Notification_RenamedGroup: String { return self._s[3370]! } - public var PeopleNearby_Groups: String { return self._s[3371]! } - public var Checkout_PayWithFaceId: String { return self._s[3372]! } - public var Channel_BanList_BlockedTitle: String { return self._s[3373]! } - public var SettingsSearch_Synonyms_Notifications_InAppNotificationsSound: String { return self._s[3375]! } - public var Checkout_WebConfirmation_Title: String { return self._s[3376]! } - public var Notifications_MessageNotificationsAlert: String { return self._s[3377]! } + public var ApplyLanguage_UnsufficientDataTitle: String { return self._s[3372]! } + public var Conversation_LinkDialogSave: String { return self._s[3373]! } + public var GroupInfo_ActionRestrict: String { return self._s[3374]! } + public var Checkout_Title: String { return self._s[3375]! } + public var Channel_DiscussionGroup_HeaderLabel: String { return self._s[3377]! } + public var Channel_AdminLog_CanChangeInfo: String { return self._s[3379]! } + public var Notification_RenamedGroup: String { return self._s[3380]! } + public var PeopleNearby_Groups: String { return self._s[3381]! } + public var Checkout_PayWithFaceId: String { return self._s[3382]! } + public var Channel_BanList_BlockedTitle: String { return self._s[3383]! } + public var SettingsSearch_Synonyms_Notifications_InAppNotificationsSound: String { return self._s[3385]! } + public var Checkout_WebConfirmation_Title: String { return self._s[3386]! } + public var Notifications_MessageNotificationsAlert: String { return self._s[3387]! } public func Activity_RemindAboutGroup(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3378]!, self._r[3378]!, [_0]) + return formatWithArgumentRanges(self._s[3388]!, self._r[3388]!, [_0]) } - public var Profile_AddToExisting: String { return self._s[3380]! } + public var Profile_AddToExisting: String { return self._s[3390]! } public func Profile_CreateEncryptedChatOutdatedError(_ _0: String, _ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3381]!, self._r[3381]!, [_0, _1]) + return formatWithArgumentRanges(self._s[3391]!, self._r[3391]!, [_0, _1]) } - public var Cache_Files: String { return self._s[3383]! } - public var Permissions_PrivacyPolicy: String { return self._s[3384]! } - public var SocksProxySetup_ConnectAndSave: String { return self._s[3385]! } - public var UserInfo_NotificationsDefaultDisabled: String { return self._s[3386]! } - public var AutoDownloadSettings_TypeContacts: String { return self._s[3388]! } - public var Appearance_ThemePreview_ChatList_1_Text: String { return self._s[3390]! } - public var Calls_NoCallsPlaceholder: String { return self._s[3391]! } + public var Cache_Files: String { return self._s[3393]! } + public var Permissions_PrivacyPolicy: String { return self._s[3394]! } + public var SocksProxySetup_ConnectAndSave: String { return self._s[3395]! } + public var UserInfo_NotificationsDefaultDisabled: String { return self._s[3396]! } + public var AutoDownloadSettings_TypeContacts: String { return self._s[3398]! } + public var Appearance_ThemePreview_ChatList_1_Text: String { return self._s[3400]! } + public var Calls_NoCallsPlaceholder: String { return self._s[3401]! } public func Wallet_Receive_ShareInvoiceUrlInfo(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3392]!, self._r[3392]!, [_0]) + return formatWithArgumentRanges(self._s[3402]!, self._r[3402]!, [_0]) } - public var Channel_Username_RevokeExistingUsernamesInfo: String { return self._s[3393]! } - public var VoiceOver_AttachMedia: String { return self._s[3396]! } - public var Notifications_ExceptionsGroupPlaceholder: String { return self._s[3397]! } + public var Channel_Username_RevokeExistingUsernamesInfo: String { return self._s[3403]! } + public var VoiceOver_AttachMedia: String { return self._s[3406]! } + public var ChatList_EmptyChatFilterList: String { return self._s[3407]! } + public var Notifications_ExceptionsGroupPlaceholder: String { return self._s[3408]! } public func PUSH_CHAT_MESSAGE_INVOICE(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3398]!, self._r[3398]!, [_1, _2, _3]) + return formatWithArgumentRanges(self._s[3409]!, self._r[3409]!, [_1, _2, _3]) } - public var SettingsSearch_Synonyms_Notifications_GroupNotificationsSound: String { return self._s[3399]! } - public var Conversation_SetReminder_Title: String { return self._s[3400]! } - public var Passport_FieldAddressHelp: String { return self._s[3401]! } - public var Privacy_GroupsAndChannels_InviteToChannelMultipleError: String { return self._s[3402]! } - public var PUSH_REMINDER_TITLE: String { return self._s[3403]! } + public var SettingsSearch_Synonyms_Notifications_GroupNotificationsSound: String { return self._s[3410]! } + public var Conversation_SetReminder_Title: String { return self._s[3411]! } + public var Passport_FieldAddressHelp: String { return self._s[3412]! } + public var Privacy_GroupsAndChannels_InviteToChannelMultipleError: String { return self._s[3413]! } + public var PUSH_REMINDER_TITLE: String { return self._s[3414]! } public func Login_TermsOfService_ProceedBot(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3404]!, self._r[3404]!, [_0]) + return formatWithArgumentRanges(self._s[3415]!, self._r[3415]!, [_0]) } - public var Channel_AdminLog_EmptyTitle: String { return self._s[3405]! } - public var Privacy_Calls_NeverAllow_Title: String { return self._s[3406]! } - public var Login_UnknownError: String { return self._s[3407]! } - public var Group_UpgradeNoticeText2: String { return self._s[3410]! } - public var Watch_Compose_AddContact: String { return self._s[3411]! } - public var ClearCache_StorageServiceFiles: String { return self._s[3412]! } - public var Web_Error: String { return self._s[3413]! } - public var Gif_Search: String { return self._s[3414]! } - public var Profile_MessageLifetime1h: String { return self._s[3415]! } - public var CheckoutInfo_ReceiverInfoEmailPlaceholder: String { return self._s[3416]! } - public var Channel_Username_CheckingUsername: String { return self._s[3417]! } - public var CallFeedback_ReasonSilentRemote: String { return self._s[3418]! } - public var AutoDownloadSettings_TypeChannels: String { return self._s[3419]! } - public var Channel_AboutItem: String { return self._s[3420]! } - public var Privacy_GroupsAndChannels_AlwaysAllow_Placeholder: String { return self._s[3422]! } - public var VoiceOver_Chat_VoiceMessage: String { return self._s[3423]! } - public var GroupInfo_SharedMedia: String { return self._s[3424]! } + public var Channel_AdminLog_EmptyTitle: String { return self._s[3416]! } + public var Privacy_Calls_NeverAllow_Title: String { return self._s[3417]! } + public var Login_UnknownError: String { return self._s[3418]! } + public var Group_UpgradeNoticeText2: String { return self._s[3421]! } + public var Watch_Compose_AddContact: String { return self._s[3422]! } + public var ClearCache_StorageServiceFiles: String { return self._s[3423]! } + public var Web_Error: String { return self._s[3424]! } + public var Gif_Search: String { return self._s[3425]! } + public var Profile_MessageLifetime1h: String { return self._s[3426]! } + public var CheckoutInfo_ReceiverInfoEmailPlaceholder: String { return self._s[3427]! } + public var Channel_Username_CheckingUsername: String { return self._s[3428]! } + public var CallFeedback_ReasonSilentRemote: String { return self._s[3429]! } + public var AutoDownloadSettings_TypeChannels: String { return self._s[3430]! } + public var Channel_AboutItem: String { return self._s[3431]! } + public var Privacy_GroupsAndChannels_AlwaysAllow_Placeholder: String { return self._s[3433]! } + public var VoiceOver_Chat_VoiceMessage: String { return self._s[3434]! } + public var GroupInfo_SharedMedia: String { return self._s[3435]! } public func Channel_AdminLog_MessagePromotedName(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3425]!, self._r[3425]!, [_1]) + return formatWithArgumentRanges(self._s[3436]!, self._r[3436]!, [_1]) } - public var Call_PhoneCallInProgressMessage: String { return self._s[3426]! } + public var Call_PhoneCallInProgressMessage: String { return self._s[3437]! } public func PUSH_CHANNEL_ALBUM(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3427]!, self._r[3427]!, [_1]) + return formatWithArgumentRanges(self._s[3438]!, self._r[3438]!, [_1]) } - public var ChatList_UndoArchiveRevealedText: String { return self._s[3428]! } - public var GroupInfo_InviteLink_RevokeAlert_Text: String { return self._s[3429]! } - public var Conversation_SearchByName_Placeholder: String { return self._s[3430]! } - public var CreatePoll_AddOption: String { return self._s[3431]! } - public var GroupInfo_Permissions_SearchPlaceholder: String { return self._s[3432]! } - public var Group_UpgradeNoticeHeader: String { return self._s[3433]! } - public var Channel_Management_AddModerator: String { return self._s[3434]! } - public var AutoDownloadSettings_MaxFileSize: String { return self._s[3435]! } - public var StickerPacksSettings_ShowStickersButton: String { return self._s[3436]! } - public var Wallet_Info_RefreshErrorNetworkText: String { return self._s[3437]! } - public var Theme_Colors_Background: String { return self._s[3438]! } - public var NotificationsSound_Hello: String { return self._s[3440]! } - public var SocksProxySetup_SavedProxies: String { return self._s[3441]! } - public var Channel_Stickers_Placeholder: String { return self._s[3443]! } + public var ChatList_UndoArchiveRevealedText: String { return self._s[3439]! } + public var GroupInfo_InviteLink_RevokeAlert_Text: String { return self._s[3440]! } + public var Conversation_SearchByName_Placeholder: String { return self._s[3441]! } + public var CreatePoll_AddOption: String { return self._s[3442]! } + public var GroupInfo_Permissions_SearchPlaceholder: String { return self._s[3443]! } + public var Group_UpgradeNoticeHeader: String { return self._s[3444]! } + public var Channel_Management_AddModerator: String { return self._s[3445]! } + public var AutoDownloadSettings_MaxFileSize: String { return self._s[3446]! } + public var StickerPacksSettings_ShowStickersButton: String { return self._s[3447]! } + public var Wallet_Info_RefreshErrorNetworkText: String { return self._s[3448]! } + public var Theme_Colors_Background: String { return self._s[3449]! } + public var NotificationsSound_Hello: String { return self._s[3451]! } + public var SocksProxySetup_SavedProxies: String { return self._s[3452]! } + public var Channel_Stickers_Placeholder: String { return self._s[3454]! } public func Login_EmailCodeBody(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3444]!, self._r[3444]!, [_0]) + return formatWithArgumentRanges(self._s[3455]!, self._r[3455]!, [_0]) } - public var PrivacyPolicy_DeclineDeclineAndDelete: String { return self._s[3445]! } - public var Channel_Management_AddModeratorHelp: String { return self._s[3446]! } - public var ContactInfo_BirthdayLabel: String { return self._s[3447]! } - public var ChangePhoneNumberCode_RequestingACall: String { return self._s[3448]! } - public var AutoDownloadSettings_Channels: String { return self._s[3449]! } - public var Passport_Language_mn: String { return self._s[3450]! } - public var Notifications_ResetAllNotificationsHelp: String { return self._s[3453]! } - public var GroupInfo_Permissions_SlowmodeValue_Off: String { return self._s[3454]! } - public var Passport_Language_ja: String { return self._s[3456]! } - public var Settings_About_Title: String { return self._s[3457]! } - public var Settings_NotificationsAndSounds: String { return self._s[3458]! } - public var ChannelInfo_DeleteGroup: String { return self._s[3459]! } - public var Settings_BlockedUsers: String { return self._s[3460]! } + public var PrivacyPolicy_DeclineDeclineAndDelete: String { return self._s[3456]! } + public var Channel_Management_AddModeratorHelp: String { return self._s[3457]! } + public var ContactInfo_BirthdayLabel: String { return self._s[3458]! } + public var ChangePhoneNumberCode_RequestingACall: String { return self._s[3459]! } + public var AutoDownloadSettings_Channels: String { return self._s[3460]! } + public var Passport_Language_mn: String { return self._s[3461]! } + public var Notifications_ResetAllNotificationsHelp: String { return self._s[3464]! } + public var GroupInfo_Permissions_SlowmodeValue_Off: String { return self._s[3465]! } + public var Passport_Language_ja: String { return self._s[3467]! } + public var Settings_About_Title: String { return self._s[3468]! } + public var Settings_NotificationsAndSounds: String { return self._s[3469]! } + public var ChannelInfo_DeleteGroup: String { return self._s[3470]! } + public var Settings_BlockedUsers: String { return self._s[3471]! } public func Time_MonthOfYear_m4(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3461]!, self._r[3461]!, [_0]) + return formatWithArgumentRanges(self._s[3472]!, self._r[3472]!, [_0]) } - public var EditTheme_Create_Preview_OutgoingText: String { return self._s[3462]! } - public var Wallet_Weekday_Today: String { return self._s[3463]! } - public var AutoDownloadSettings_PreloadVideo: String { return self._s[3464]! } - public var Widget_ApplicationLocked: String { return self._s[3465]! } - public var Passport_Address_AddResidentialAddress: String { return self._s[3466]! } - public var Channel_Username_Title: String { return self._s[3467]! } + public var EditTheme_Create_Preview_OutgoingText: String { return self._s[3473]! } + public var Wallet_Weekday_Today: String { return self._s[3474]! } + public var AutoDownloadSettings_PreloadVideo: String { return self._s[3475]! } + public var Widget_ApplicationLocked: String { return self._s[3476]! } + public var Passport_Address_AddResidentialAddress: String { return self._s[3477]! } + public var Channel_Username_Title: String { return self._s[3478]! } public func Notification_RemovedGroupPhoto(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3468]!, self._r[3468]!, [_0]) + return formatWithArgumentRanges(self._s[3479]!, self._r[3479]!, [_0]) } - public var AttachmentMenu_File: String { return self._s[3470]! } - public var AppleWatch_Title: String { return self._s[3471]! } - public var Activity_RecordingVideoMessage: String { return self._s[3472]! } + public var AttachmentMenu_File: String { return self._s[3481]! } + public var AppleWatch_Title: String { return self._s[3482]! } + public var Activity_RecordingVideoMessage: String { return self._s[3483]! } public func Channel_DiscussionGroup_PublicChannelLink(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3473]!, self._r[3473]!, [_1, _2]) + return formatWithArgumentRanges(self._s[3484]!, self._r[3484]!, [_1, _2]) } - public var Theme_Colors_Messages: String { return self._s[3474]! } - public var Weekday_Saturday: String { return self._s[3475]! } - public var WallpaperPreview_SwipeColorsTopText: String { return self._s[3476]! } - public var Profile_CreateEncryptedChatError: String { return self._s[3477]! } - public var Common_Next: String { return self._s[3479]! } - public var Channel_Stickers_YourStickers: String { return self._s[3481]! } - public var Message_Theme: String { return self._s[3482]! } - public var Call_AudioRouteHeadphones: String { return self._s[3483]! } - public var TwoStepAuth_EnterPasswordForgot: String { return self._s[3485]! } - public var Watch_Contacts_NoResults: String { return self._s[3487]! } - public var PhotoEditor_TintTool: String { return self._s[3490]! } - public var LoginPassword_ResetAccount: String { return self._s[3492]! } - public var Settings_SavedMessages: String { return self._s[3493]! } - public var SettingsSearch_Synonyms_Appearance_Animations: String { return self._s[3494]! } - public var Bot_GenericSupportStatus: String { return self._s[3495]! } - public var StickerPack_Add: String { return self._s[3496]! } - public var Checkout_TotalAmount: String { return self._s[3497]! } - public var Your_cards_number_is_invalid: String { return self._s[3498]! } - public var SettingsSearch_Synonyms_Appearance_AutoNightTheme: String { return self._s[3499]! } - public var VoiceOver_Chat_VideoMessage: String { return self._s[3500]! } + public var Theme_Colors_Messages: String { return self._s[3485]! } + public var Weekday_Saturday: String { return self._s[3486]! } + public var WallpaperPreview_SwipeColorsTopText: String { return self._s[3487]! } + public var Profile_CreateEncryptedChatError: String { return self._s[3488]! } + public var Common_Next: String { return self._s[3490]! } + public var Channel_Stickers_YourStickers: String { return self._s[3492]! } + public var Message_Theme: String { return self._s[3493]! } + public var Call_AudioRouteHeadphones: String { return self._s[3494]! } + public var TwoStepAuth_EnterPasswordForgot: String { return self._s[3496]! } + public var Watch_Contacts_NoResults: String { return self._s[3498]! } + public var PhotoEditor_TintTool: String { return self._s[3501]! } + public var LoginPassword_ResetAccount: String { return self._s[3503]! } + public var Settings_SavedMessages: String { return self._s[3504]! } + public var SettingsSearch_Synonyms_Appearance_Animations: String { return self._s[3505]! } + public var Bot_GenericSupportStatus: String { return self._s[3506]! } + public var StickerPack_Add: String { return self._s[3507]! } + public var Checkout_TotalAmount: String { return self._s[3508]! } + public var Your_cards_number_is_invalid: String { return self._s[3509]! } + public var SettingsSearch_Synonyms_Appearance_AutoNightTheme: String { return self._s[3510]! } + public var VoiceOver_Chat_VideoMessage: String { return self._s[3511]! } public func ChangePhoneNumberCode_CallTimer(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3501]!, self._r[3501]!, [_0]) + return formatWithArgumentRanges(self._s[3512]!, self._r[3512]!, [_0]) } public func GroupPermission_AddedInfo(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3502]!, self._r[3502]!, [_1, _2]) + return formatWithArgumentRanges(self._s[3513]!, self._r[3513]!, [_1, _2]) } - public var ChatSettings_ConnectionType_UseSocks5: String { return self._s[3503]! } + public var ChatSettings_ConnectionType_UseSocks5: String { return self._s[3514]! } public func PUSH_CHAT_PHOTO_EDITED(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3505]!, self._r[3505]!, [_1, _2]) + return formatWithArgumentRanges(self._s[3516]!, self._r[3516]!, [_1, _2]) } public func Conversation_RestrictedTextTimed(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3506]!, self._r[3506]!, [_0]) + return formatWithArgumentRanges(self._s[3517]!, self._r[3517]!, [_0]) } - public var GroupInfo_InviteLink_ShareLink: String { return self._s[3507]! } - public var StickerPack_Share: String { return self._s[3508]! } - public var Passport_DeleteAddress: String { return self._s[3509]! } - public var Settings_Passport: String { return self._s[3510]! } - public var SharedMedia_EmptyFilesText: String { return self._s[3511]! } - public var Conversation_DeleteMessagesForMe: String { return self._s[3512]! } - public var PasscodeSettings_AutoLock_IfAwayFor_1hour: String { return self._s[3513]! } - public var Contacts_PermissionsText: String { return self._s[3514]! } - public var Group_Setup_HistoryVisible: String { return self._s[3515]! } - public var Wallet_Month_ShortDecember: String { return self._s[3517]! } - public var Channel_EditAdmin_PermissionEnabledByDefault: String { return self._s[3518]! } - public var Passport_Address_AddRentalAgreement: String { return self._s[3519]! } - public var SocksProxySetup_Title: String { return self._s[3520]! } - public var Notification_Mute1h: String { return self._s[3521]! } + public var GroupInfo_InviteLink_ShareLink: String { return self._s[3518]! } + public var StickerPack_Share: String { return self._s[3519]! } + public var Passport_DeleteAddress: String { return self._s[3520]! } + public var Settings_Passport: String { return self._s[3521]! } + public var SharedMedia_EmptyFilesText: String { return self._s[3522]! } + public var Conversation_DeleteMessagesForMe: String { return self._s[3523]! } + public var PasscodeSettings_AutoLock_IfAwayFor_1hour: String { return self._s[3524]! } + public var Contacts_PermissionsText: String { return self._s[3525]! } + public var Group_Setup_HistoryVisible: String { return self._s[3526]! } + public var Wallet_Month_ShortDecember: String { return self._s[3528]! } + public var Channel_EditAdmin_PermissionEnabledByDefault: String { return self._s[3529]! } + public var Passport_Address_AddRentalAgreement: String { return self._s[3530]! } + public var SocksProxySetup_Title: String { return self._s[3531]! } + public var Notification_Mute1h: String { return self._s[3532]! } public func Passport_Email_CodeHelp(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3522]!, self._r[3522]!, [_0]) + return formatWithArgumentRanges(self._s[3533]!, self._r[3533]!, [_0]) } - public var NotificationSettings_ShowNotificationsAllAccountsInfoOff: String { return self._s[3523]! } + public var NotificationSettings_ShowNotificationsAllAccountsInfoOff: String { return self._s[3534]! } public func PUSH_PINNED_GEOLIVE(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3524]!, self._r[3524]!, [_1]) + return formatWithArgumentRanges(self._s[3535]!, self._r[3535]!, [_1]) } - public var FastTwoStepSetup_PasswordSection: String { return self._s[3525]! } - public var NetworkUsageSettings_ResetStatsConfirmation: String { return self._s[3528]! } - public var InfoPlist_NSFaceIDUsageDescription: String { return self._s[3530]! } - public var DialogList_NoMessagesText: String { return self._s[3531]! } - public var Privacy_ContactsResetConfirmation: String { return self._s[3532]! } - public var Privacy_Calls_P2PHelp: String { return self._s[3533]! } - public var Channel_DiscussionGroup_SearchPlaceholder: String { return self._s[3535]! } - public var Your_cards_expiration_year_is_invalid: String { return self._s[3536]! } - public var Common_TakePhotoOrVideo: String { return self._s[3537]! } - public var Wallet_Words_Text: String { return self._s[3538]! } - public var Call_StatusBusy: String { return self._s[3539]! } - public var Conversation_PinnedMessage: String { return self._s[3540]! } - public var AutoDownloadSettings_VoiceMessagesTitle: String { return self._s[3541]! } - public var Wallet_Configuration_BlockchainNameChangedProceed: String { return self._s[3542]! } - public var TwoStepAuth_SetupPasswordConfirmFailed: String { return self._s[3543]! } - public var Undo_ChatCleared: String { return self._s[3544]! } - public var AppleWatch_ReplyPresets: String { return self._s[3545]! } - public var Passport_DiscardMessageDescription: String { return self._s[3547]! } - public var Login_NetworkError: String { return self._s[3548]! } + public var FastTwoStepSetup_PasswordSection: String { return self._s[3536]! } + public var NetworkUsageSettings_ResetStatsConfirmation: String { return self._s[3539]! } + public var InfoPlist_NSFaceIDUsageDescription: String { return self._s[3541]! } + public var DialogList_NoMessagesText: String { return self._s[3542]! } + public var Privacy_ContactsResetConfirmation: String { return self._s[3543]! } + public var Privacy_Calls_P2PHelp: String { return self._s[3544]! } + public var Channel_DiscussionGroup_SearchPlaceholder: String { return self._s[3546]! } + public var Your_cards_expiration_year_is_invalid: String { return self._s[3547]! } + public var Common_TakePhotoOrVideo: String { return self._s[3548]! } + public var Wallet_Words_Text: String { return self._s[3549]! } + public var Call_StatusBusy: String { return self._s[3550]! } + public var Conversation_PinnedMessage: String { return self._s[3551]! } + public var AutoDownloadSettings_VoiceMessagesTitle: String { return self._s[3552]! } + public var ChatList_EmptyChatListNewMessage: String { return self._s[3553]! } + public var Wallet_Configuration_BlockchainNameChangedProceed: String { return self._s[3554]! } + public var TwoStepAuth_SetupPasswordConfirmFailed: String { return self._s[3555]! } + public var Undo_ChatCleared: String { return self._s[3556]! } + public var AppleWatch_ReplyPresets: String { return self._s[3557]! } + public var Passport_DiscardMessageDescription: String { return self._s[3559]! } + public var Login_NetworkError: String { return self._s[3560]! } public func Notification_PinnedRoundMessage(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3549]!, self._r[3549]!, [_0]) + return formatWithArgumentRanges(self._s[3561]!, self._r[3561]!, [_0]) } public func Channel_AdminLog_MessageRemovedChannelUsername(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3550]!, self._r[3550]!, [_0]) + return formatWithArgumentRanges(self._s[3562]!, self._r[3562]!, [_0]) } - public var SocksProxySetup_PasswordPlaceholder: String { return self._s[3551]! } - public var Wallet_WordCheck_ViewWords: String { return self._s[3553]! } - public var Login_ResetAccountProtected_LimitExceeded: String { return self._s[3554]! } + public var SocksProxySetup_PasswordPlaceholder: String { return self._s[3563]! } + public var Wallet_WordCheck_ViewWords: String { return self._s[3565]! } + public var Login_ResetAccountProtected_LimitExceeded: String { return self._s[3566]! } public func Watch_LastSeen_YesterdayAt(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3556]!, self._r[3556]!, [_0]) + return formatWithArgumentRanges(self._s[3568]!, self._r[3568]!, [_0]) } - public var Call_ConnectionErrorMessage: String { return self._s[3557]! } - public var VoiceOver_Chat_Music: String { return self._s[3558]! } - public var SettingsSearch_Synonyms_Notifications_MessageNotificationsSound: String { return self._s[3559]! } - public var Compose_GroupTokenListPlaceholder: String { return self._s[3561]! } - public var ConversationMedia_Title: String { return self._s[3562]! } - public var EncryptionKey_Title: String { return self._s[3564]! } - public var TwoStepAuth_EnterPasswordTitle: String { return self._s[3565]! } - public var Notification_Exceptions_AddException: String { return self._s[3566]! } - public var PrivacySettings_BlockedPeersEmpty: String { return self._s[3567]! } - public var Profile_MessageLifetime1m: String { return self._s[3568]! } + public var Call_ConnectionErrorMessage: String { return self._s[3569]! } + public var VoiceOver_Chat_Music: String { return self._s[3570]! } + public var SettingsSearch_Synonyms_Notifications_MessageNotificationsSound: String { return self._s[3571]! } + public var Compose_GroupTokenListPlaceholder: String { return self._s[3573]! } + public var ConversationMedia_Title: String { return self._s[3574]! } + public var EncryptionKey_Title: String { return self._s[3576]! } + public var TwoStepAuth_EnterPasswordTitle: String { return self._s[3577]! } + public var Notification_Exceptions_AddException: String { return self._s[3578]! } + public var PrivacySettings_BlockedPeersEmpty: String { return self._s[3579]! } + public var Profile_MessageLifetime1m: String { return self._s[3580]! } public func Channel_AdminLog_MessageUnkickedName(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3569]!, self._r[3569]!, [_1]) + return formatWithArgumentRanges(self._s[3581]!, self._r[3581]!, [_1]) } - public var Month_GenMay: String { return self._s[3570]! } + public var Month_GenMay: String { return self._s[3582]! } public func LiveLocationUpdated_TodayAt(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3571]!, self._r[3571]!, [_0]) + return formatWithArgumentRanges(self._s[3583]!, self._r[3583]!, [_0]) } - public var PeopleNearby_Users: String { return self._s[3572]! } - public var Wallet_Send_AddressInfo: String { return self._s[3573]! } - public var ChannelMembers_WhoCanAddMembersAllHelp: String { return self._s[3574]! } - public var AutoDownloadSettings_ResetSettings: String { return self._s[3575]! } + public var PeopleNearby_Users: String { return self._s[3584]! } + public var Wallet_Send_AddressInfo: String { return self._s[3585]! } + public var ChannelMembers_WhoCanAddMembersAllHelp: String { return self._s[3586]! } + public var AutoDownloadSettings_ResetSettings: String { return self._s[3587]! } public func Wallet_Updated_AtDate(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3577]!, self._r[3577]!, [_0]) + return formatWithArgumentRanges(self._s[3589]!, self._r[3589]!, [_0]) } - public var Conversation_EmptyPlaceholder: String { return self._s[3578]! } - public var Passport_Address_AddPassportRegistration: String { return self._s[3579]! } - public var Notifications_ChannelNotificationsAlert: String { return self._s[3580]! } - public var ChatSettings_AutoDownloadUsingCellular: String { return self._s[3581]! } - public var Camera_TapAndHoldForVideo: String { return self._s[3582]! } - public var Channel_JoinChannel: String { return self._s[3584]! } - public var Appearance_Animations: String { return self._s[3587]! } + public var Conversation_EmptyPlaceholder: String { return self._s[3590]! } + public var Passport_Address_AddPassportRegistration: String { return self._s[3591]! } + public var Notifications_ChannelNotificationsAlert: String { return self._s[3592]! } + public var ChatSettings_AutoDownloadUsingCellular: String { return self._s[3593]! } + public var Camera_TapAndHoldForVideo: String { return self._s[3594]! } + public var Channel_JoinChannel: String { return self._s[3596]! } + public var Appearance_Animations: String { return self._s[3599]! } public func Notification_MessageLifetimeChanged(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3588]!, self._r[3588]!, [_1, _2]) + return formatWithArgumentRanges(self._s[3600]!, self._r[3600]!, [_1, _2]) } - public var Stickers_GroupStickers: String { return self._s[3590]! } - public var Appearance_ShareTheme: String { return self._s[3591]! } - public var TwoFactorSetup_Hint_Placeholder: String { return self._s[3592]! } - public var ConvertToSupergroup_HelpTitle: String { return self._s[3594]! } - public var StickerPackActionInfo_RemovedTitle: String { return self._s[3595]! } - public var Passport_Address_Street: String { return self._s[3596]! } - public var Conversation_AddContact: String { return self._s[3597]! } - public var Login_PhonePlaceholder: String { return self._s[3598]! } - public var Channel_Members_InviteLink: String { return self._s[3600]! } - public var Bot_Stop: String { return self._s[3601]! } - public var SettingsSearch_Synonyms_Proxy_UseForCalls: String { return self._s[3603]! } - public var Notification_PassportValueAddress: String { return self._s[3604]! } - public var Month_ShortJuly: String { return self._s[3605]! } - public var Passport_Address_TypeTemporaryRegistrationUploadScan: String { return self._s[3606]! } - public var Channel_AdminLog_BanSendMedia: String { return self._s[3607]! } - public var Passport_Identity_ReverseSide: String { return self._s[3608]! } - public var Watch_Stickers_Recents: String { return self._s[3611]! } - public var PrivacyLastSeenSettings_EmpryUsersPlaceholder: String { return self._s[3613]! } - public var Map_SendThisLocation: String { return self._s[3614]! } + public var Stickers_GroupStickers: String { return self._s[3602]! } + public var Appearance_ShareTheme: String { return self._s[3603]! } + public var TwoFactorSetup_Hint_Placeholder: String { return self._s[3604]! } + public var ConvertToSupergroup_HelpTitle: String { return self._s[3606]! } + public var StickerPackActionInfo_RemovedTitle: String { return self._s[3607]! } + public var Passport_Address_Street: String { return self._s[3608]! } + public var Conversation_AddContact: String { return self._s[3609]! } + public var Login_PhonePlaceholder: String { return self._s[3610]! } + public var Channel_Members_InviteLink: String { return self._s[3612]! } + public var Bot_Stop: String { return self._s[3613]! } + public var SettingsSearch_Synonyms_Proxy_UseForCalls: String { return self._s[3615]! } + public var Notification_PassportValueAddress: String { return self._s[3616]! } + public var Month_ShortJuly: String { return self._s[3617]! } + public var Passport_Address_TypeTemporaryRegistrationUploadScan: String { return self._s[3618]! } + public var Channel_AdminLog_BanSendMedia: String { return self._s[3619]! } + public var Passport_Identity_ReverseSide: String { return self._s[3620]! } + public var Watch_Stickers_Recents: String { return self._s[3623]! } + public var PrivacyLastSeenSettings_EmpryUsersPlaceholder: String { return self._s[3625]! } + public var Map_SendThisLocation: String { return self._s[3626]! } public func Time_MonthOfYear_m1(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3615]!, self._r[3615]!, [_0]) + return formatWithArgumentRanges(self._s[3627]!, self._r[3627]!, [_0]) } public func InviteText_SingleContact(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3616]!, self._r[3616]!, [_0]) + return formatWithArgumentRanges(self._s[3628]!, self._r[3628]!, [_0]) } - public var ConvertToSupergroup_Note: String { return self._s[3617]! } - public var Wallet_Intro_NotNow: String { return self._s[3618]! } + public var ConvertToSupergroup_Note: String { return self._s[3629]! } + public var Wallet_Intro_NotNow: String { return self._s[3630]! } public func FileSize_MB(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3619]!, self._r[3619]!, [_0]) + return formatWithArgumentRanges(self._s[3631]!, self._r[3631]!, [_0]) } - public var NetworkUsageSettings_GeneralDataSection: String { return self._s[3620]! } + public var NetworkUsageSettings_GeneralDataSection: String { return self._s[3632]! } public func Compatibility_SecretMediaVersionTooLow(_ _0: String, _ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3621]!, self._r[3621]!, [_0, _1]) + return formatWithArgumentRanges(self._s[3633]!, self._r[3633]!, [_0, _1]) } - public var Login_CallRequestState3: String { return self._s[3623]! } - public var Wallpaper_SearchShort: String { return self._s[3624]! } - public var SettingsSearch_Synonyms_Appearance_ColorTheme: String { return self._s[3626]! } - public var PasscodeSettings_UnlockWithFaceId: String { return self._s[3627]! } - public var Channel_BotDoesntSupportGroups: String { return self._s[3628]! } + public var Login_CallRequestState3: String { return self._s[3635]! } + public var Wallpaper_SearchShort: String { return self._s[3636]! } + public var SettingsSearch_Synonyms_Appearance_ColorTheme: String { return self._s[3638]! } + public var PasscodeSettings_UnlockWithFaceId: String { return self._s[3639]! } + public var Channel_BotDoesntSupportGroups: String { return self._s[3640]! } public func PUSH_CHAT_MESSAGE_GEOLIVE(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3629]!, self._r[3629]!, [_1, _2]) + return formatWithArgumentRanges(self._s[3641]!, self._r[3641]!, [_1, _2]) } - public var Channel_AdminLogFilter_Title: String { return self._s[3630]! } - public var Appearance_ThemePreview_Chat_4_Text: String { return self._s[3632]! } - public var Notifications_GroupNotificationsExceptions: String { return self._s[3635]! } + public var Channel_AdminLogFilter_Title: String { return self._s[3642]! } + public var Appearance_ThemePreview_Chat_4_Text: String { return self._s[3644]! } + public var Notifications_GroupNotificationsExceptions: String { return self._s[3647]! } public func FileSize_B(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3636]!, self._r[3636]!, [_0]) + return formatWithArgumentRanges(self._s[3648]!, self._r[3648]!, [_0]) } - public var Passport_CorrectErrors: String { return self._s[3637]! } - public var VoiceOver_Chat_YourAnonymousPoll: String { return self._s[3638]! } + public var Passport_CorrectErrors: String { return self._s[3649]! } + public var VoiceOver_Chat_YourAnonymousPoll: String { return self._s[3650]! } public func Channel_MessageTitleUpdated(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3639]!, self._r[3639]!, [_0]) + return formatWithArgumentRanges(self._s[3651]!, self._r[3651]!, [_0]) } - public var Map_SendMyCurrentLocation: String { return self._s[3640]! } - public var Channel_DiscussionGroup: String { return self._s[3641]! } - public var TwoFactorSetup_Email_SkipConfirmationSkip: String { return self._s[3642]! } + public var Map_SendMyCurrentLocation: String { return self._s[3652]! } + public var Channel_DiscussionGroup: String { return self._s[3653]! } + public var TwoFactorSetup_Email_SkipConfirmationSkip: String { return self._s[3654]! } public func PUSH_PINNED_CONTACT(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3643]!, self._r[3643]!, [_1, _2]) + return formatWithArgumentRanges(self._s[3655]!, self._r[3655]!, [_1, _2]) } - public var SharedMedia_SearchNoResults: String { return self._s[3644]! } - public var Permissions_NotificationsText_v0: String { return self._s[3645]! } - public var Channel_EditAdmin_PermissionDeleteMessagesOfOthers: String { return self._s[3646]! } - public var Appearance_AppIcon: String { return self._s[3647]! } - public var Appearance_ThemePreview_ChatList_3_AuthorName: String { return self._s[3648]! } - public var LoginPassword_FloodError: String { return self._s[3649]! } - public var Wallet_Send_OwnAddressAlertProceed: String { return self._s[3651]! } - public var Group_Setup_HistoryHiddenHelp: String { return self._s[3652]! } + public var SharedMedia_SearchNoResults: String { return self._s[3656]! } + public var Permissions_NotificationsText_v0: String { return self._s[3657]! } + public var Channel_EditAdmin_PermissionDeleteMessagesOfOthers: String { return self._s[3658]! } + public var Appearance_AppIcon: String { return self._s[3659]! } + public var Appearance_ThemePreview_ChatList_3_AuthorName: String { return self._s[3660]! } + public var LoginPassword_FloodError: String { return self._s[3661]! } + public var Wallet_Send_OwnAddressAlertProceed: String { return self._s[3663]! } + public var Group_Setup_HistoryHiddenHelp: String { return self._s[3664]! } public func TwoStepAuth_PendingEmailHelp(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3653]!, self._r[3653]!, [_0]) + return formatWithArgumentRanges(self._s[3665]!, self._r[3665]!, [_0]) } - public var Passport_Language_bn: String { return self._s[3654]! } + public var Passport_Language_bn: String { return self._s[3666]! } public func DialogList_SingleUploadingPhotoSuffix(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3655]!, self._r[3655]!, [_0]) + return formatWithArgumentRanges(self._s[3667]!, self._r[3667]!, [_0]) } - public var ChatList_Context_Pin: String { return self._s[3656]! } + public var ChatList_Context_Pin: String { return self._s[3668]! } public func Notification_PinnedAudioMessage(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3657]!, self._r[3657]!, [_0]) + return formatWithArgumentRanges(self._s[3669]!, self._r[3669]!, [_0]) } public func Channel_AdminLog_MessageChangedGroupStickerPack(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3658]!, self._r[3658]!, [_0]) + return formatWithArgumentRanges(self._s[3670]!, self._r[3670]!, [_0]) } - public var Wallet_Navigation_Close: String { return self._s[3659]! } - public var GroupInfo_InvitationLinkGroupFull: String { return self._s[3663]! } - public var Group_EditAdmin_PermissionChangeInfo: String { return self._s[3665]! } - public var Wallet_Month_GenDecember: String { return self._s[3666]! } - public var Contacts_PermissionsAllow: String { return self._s[3667]! } - public var ReportPeer_ReasonCopyright: String { return self._s[3668]! } - public var Channel_EditAdmin_PermissinAddAdminOn: String { return self._s[3669]! } - public var WallpaperPreview_Pattern: String { return self._s[3670]! } - public var Paint_Duplicate: String { return self._s[3671]! } - public var Passport_Address_Country: String { return self._s[3672]! } - public var Notification_RenamedChannel: String { return self._s[3674]! } - public var ChatList_Context_Unmute: String { return self._s[3675]! } - public var CheckoutInfo_ErrorPostcodeInvalid: String { return self._s[3676]! } - public var Group_MessagePhotoUpdated: String { return self._s[3677]! } - public var Channel_BanUser_PermissionSendMedia: String { return self._s[3678]! } - public var Conversation_ContextMenuBan: String { return self._s[3679]! } - public var TwoStepAuth_EmailSent: String { return self._s[3680]! } - public var MessagePoll_NoVotes: String { return self._s[3681]! } - public var Wallet_Send_ErrorNotEnoughFundsTitle: String { return self._s[3682]! } - public var Passport_Language_is: String { return self._s[3684]! } - public var PeopleNearby_UsersEmpty: String { return self._s[3686]! } - public var Tour_Text5: String { return self._s[3687]! } + public var Wallet_Navigation_Close: String { return self._s[3671]! } + public var GroupInfo_InvitationLinkGroupFull: String { return self._s[3675]! } + public var Group_EditAdmin_PermissionChangeInfo: String { return self._s[3677]! } + public var Wallet_Month_GenDecember: String { return self._s[3678]! } + public var Contacts_PermissionsAllow: String { return self._s[3679]! } + public var ReportPeer_ReasonCopyright: String { return self._s[3680]! } + public var Channel_EditAdmin_PermissinAddAdminOn: String { return self._s[3681]! } + public var WallpaperPreview_Pattern: String { return self._s[3682]! } + public var Paint_Duplicate: String { return self._s[3683]! } + public var Passport_Address_Country: String { return self._s[3684]! } + public var Notification_RenamedChannel: String { return self._s[3686]! } + public var ChatList_Context_Unmute: String { return self._s[3687]! } + public var CheckoutInfo_ErrorPostcodeInvalid: String { return self._s[3688]! } + public var Group_MessagePhotoUpdated: String { return self._s[3689]! } + public var Channel_BanUser_PermissionSendMedia: String { return self._s[3690]! } + public var Conversation_ContextMenuBan: String { return self._s[3691]! } + public var TwoStepAuth_EmailSent: String { return self._s[3692]! } + public var MessagePoll_NoVotes: String { return self._s[3693]! } + public var Wallet_Send_ErrorNotEnoughFundsTitle: String { return self._s[3694]! } + public var Passport_Language_is: String { return self._s[3696]! } + public var PeopleNearby_UsersEmpty: String { return self._s[3698]! } + public var Tour_Text5: String { return self._s[3699]! } public func Call_GroupFormat(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3690]!, self._r[3690]!, [_1, _2]) + return formatWithArgumentRanges(self._s[3702]!, self._r[3702]!, [_1, _2]) } - public var Undo_SecretChatDeleted: String { return self._s[3691]! } - public var SocksProxySetup_ShareQRCode: String { return self._s[3692]! } + public var Undo_SecretChatDeleted: String { return self._s[3703]! } + public var SocksProxySetup_ShareQRCode: String { return self._s[3704]! } public func VoiceOver_Chat_Size(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3693]!, self._r[3693]!, [_0]) + return formatWithArgumentRanges(self._s[3705]!, self._r[3705]!, [_0]) } - public var Forward_ErrorDisabledForChat: String { return self._s[3694]! } - public var LogoutOptions_ChangePhoneNumberText: String { return self._s[3695]! } - public var Paint_Edit: String { return self._s[3697]! } - public var ScheduledMessages_ReminderNotification: String { return self._s[3699]! } - public var Undo_DeletedGroup: String { return self._s[3701]! } - public var LoginPassword_ForgotPassword: String { return self._s[3702]! } - public var Wallet_WordImport_IncorrectTitle: String { return self._s[3703]! } - public var GroupInfo_GroupNamePlaceholder: String { return self._s[3704]! } + public var Forward_ErrorDisabledForChat: String { return self._s[3706]! } + public var LogoutOptions_ChangePhoneNumberText: String { return self._s[3707]! } + public var Paint_Edit: String { return self._s[3709]! } + public var ScheduledMessages_ReminderNotification: String { return self._s[3711]! } + public var Undo_DeletedGroup: String { return self._s[3713]! } + public var LoginPassword_ForgotPassword: String { return self._s[3714]! } + public var Wallet_WordImport_IncorrectTitle: String { return self._s[3715]! } + public var GroupInfo_GroupNamePlaceholder: String { return self._s[3716]! } public func Notification_Kicked(_ _0: String, _ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3705]!, self._r[3705]!, [_0, _1]) + return formatWithArgumentRanges(self._s[3717]!, self._r[3717]!, [_0, _1]) } - public var AppWallet_TransactionInfo_FeeInfoURL: String { return self._s[3706]! } - public var Conversation_InputTextCaptionPlaceholder: String { return self._s[3707]! } - public var AutoDownloadSettings_VideoMessagesTitle: String { return self._s[3708]! } - public var Passport_Language_uz: String { return self._s[3709]! } - public var Conversation_PinMessageAlertGroup: String { return self._s[3710]! } - public var SettingsSearch_Synonyms_Privacy_GroupsAndChannels: String { return self._s[3711]! } - public var Map_StopLiveLocation: String { return self._s[3713]! } - public var VoiceOver_MessageContextSend: String { return self._s[3715]! } - public var PasscodeSettings_Help: String { return self._s[3716]! } - public var NotificationsSound_Input: String { return self._s[3717]! } - public var Share_Title: String { return self._s[3720]! } - public var LogoutOptions_Title: String { return self._s[3721]! } - public var Wallet_Send_AddressText: String { return self._s[3722]! } - public var Login_TermsOfServiceAgree: String { return self._s[3723]! } - public var Compose_NewEncryptedChatTitle: String { return self._s[3724]! } - public var Channel_AdminLog_TitleSelectedEvents: String { return self._s[3725]! } - public var Channel_EditAdmin_PermissionEditMessages: String { return self._s[3726]! } - public var EnterPasscode_EnterTitle: String { return self._s[3727]! } + public var AppWallet_TransactionInfo_FeeInfoURL: String { return self._s[3718]! } + public var Conversation_InputTextCaptionPlaceholder: String { return self._s[3719]! } + public var AutoDownloadSettings_VideoMessagesTitle: String { return self._s[3720]! } + public var Passport_Language_uz: String { return self._s[3721]! } + public var Conversation_PinMessageAlertGroup: String { return self._s[3722]! } + public var SettingsSearch_Synonyms_Privacy_GroupsAndChannels: String { return self._s[3723]! } + public var Map_StopLiveLocation: String { return self._s[3725]! } + public var VoiceOver_MessageContextSend: String { return self._s[3727]! } + public var PasscodeSettings_Help: String { return self._s[3728]! } + public var NotificationsSound_Input: String { return self._s[3729]! } + public var Share_Title: String { return self._s[3732]! } + public var LogoutOptions_Title: String { return self._s[3733]! } + public var Wallet_Send_AddressText: String { return self._s[3734]! } + public var Login_TermsOfServiceAgree: String { return self._s[3735]! } + public var Compose_NewEncryptedChatTitle: String { return self._s[3736]! } + public var Channel_AdminLog_TitleSelectedEvents: String { return self._s[3737]! } + public var Channel_EditAdmin_PermissionEditMessages: String { return self._s[3738]! } + public var EnterPasscode_EnterTitle: String { return self._s[3739]! } public func Call_PrivacyErrorMessage(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3728]!, self._r[3728]!, [_0]) + return formatWithArgumentRanges(self._s[3740]!, self._r[3740]!, [_0]) } - public var Settings_CopyPhoneNumber: String { return self._s[3729]! } - public var Conversation_AddToContacts: String { return self._s[3730]! } + public var Settings_CopyPhoneNumber: String { return self._s[3741]! } + public var Conversation_AddToContacts: String { return self._s[3742]! } public func VoiceOver_Chat_ReplyFrom(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3731]!, self._r[3731]!, [_0]) + return formatWithArgumentRanges(self._s[3743]!, self._r[3743]!, [_0]) } - public var NotificationsSound_Keys: String { return self._s[3732]! } + public var NotificationsSound_Keys: String { return self._s[3744]! } public func Call_ParticipantVersionOutdatedError(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3733]!, self._r[3733]!, [_0]) + return formatWithArgumentRanges(self._s[3745]!, self._r[3745]!, [_0]) } - public var Notification_MessageLifetime1w: String { return self._s[3734]! } - public var Message_Video: String { return self._s[3735]! } - public var AutoDownloadSettings_CellularTitle: String { return self._s[3736]! } + public var Notification_MessageLifetime1w: String { return self._s[3746]! } + public var Message_Video: String { return self._s[3747]! } + public var AutoDownloadSettings_CellularTitle: String { return self._s[3748]! } public func PUSH_CHANNEL_MESSAGE_PHOTO(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3737]!, self._r[3737]!, [_1]) + return formatWithArgumentRanges(self._s[3749]!, self._r[3749]!, [_1]) } - public var Wallet_Receive_AmountInfo: String { return self._s[3740]! } + public var Wallet_Receive_AmountInfo: String { return self._s[3752]! } + public var Stats_Overview: String { return self._s[3753]! } public func Notification_JoinedChat(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3741]!, self._r[3741]!, [_0]) + return formatWithArgumentRanges(self._s[3754]!, self._r[3754]!, [_0]) } public func PrivacySettings_LastSeenContactsPlus(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3742]!, self._r[3742]!, [_0]) + return formatWithArgumentRanges(self._s[3755]!, self._r[3755]!, [_0]) } - public var Passport_Language_mk: String { return self._s[3743]! } + public var Passport_Language_mk: String { return self._s[3756]! } public func Wallet_Time_PreciseDate_m2(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3744]!, self._r[3744]!, [_1, _2, _3]) + return formatWithArgumentRanges(self._s[3757]!, self._r[3757]!, [_1, _2, _3]) } - public var CreatePoll_CancelConfirmation: String { return self._s[3745]! } - public var MessagePoll_LabelAnonymousQuiz: String { return self._s[3746]! } - public var Conversation_SilentBroadcastTooltipOn: String { return self._s[3748]! } - public var PrivacyPolicy_Decline: String { return self._s[3749]! } - public var Passport_Identity_DoesNotExpire: String { return self._s[3750]! } - public var Channel_AdminLogFilter_EventsRestrictions: String { return self._s[3751]! } - public var AuthSessions_AddDeviceIntro_Action: String { return self._s[3752]! } - public var Permissions_SiriAllow_v0: String { return self._s[3754]! } - public var Wallet_Month_ShortAugust: String { return self._s[3755]! } - public var Appearance_ThemeCarouselNight: String { return self._s[3756]! } + public var CreatePoll_CancelConfirmation: String { return self._s[3758]! } + public var MessagePoll_LabelAnonymousQuiz: String { return self._s[3759]! } + public var Conversation_SilentBroadcastTooltipOn: String { return self._s[3761]! } + public var PrivacyPolicy_Decline: String { return self._s[3762]! } + public var Passport_Identity_DoesNotExpire: String { return self._s[3763]! } + public var Channel_AdminLogFilter_EventsRestrictions: String { return self._s[3764]! } + public var AuthSessions_AddDeviceIntro_Action: String { return self._s[3765]! } + public var Permissions_SiriAllow_v0: String { return self._s[3767]! } + public var Wallet_Month_ShortAugust: String { return self._s[3768]! } + public var Appearance_ThemeCarouselNight: String { return self._s[3769]! } public func LOCAL_CHAT_MESSAGE_FWDS(_ _1: String, _ _2: Int) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3757]!, self._r[3757]!, [_1, "\(_2)"]) + return formatWithArgumentRanges(self._s[3770]!, self._r[3770]!, [_1, "\(_2)"]) } public func Notification_RenamedChat(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3758]!, self._r[3758]!, [_0]) + return formatWithArgumentRanges(self._s[3771]!, self._r[3771]!, [_0]) } - public var Paint_Regular: String { return self._s[3759]! } - public var ChatSettings_AutoDownloadReset: String { return self._s[3760]! } - public var SocksProxySetup_ShareLink: String { return self._s[3761]! } - public var Wallet_Qr_Title: String { return self._s[3762]! } - public var BlockedUsers_SelectUserTitle: String { return self._s[3763]! } - public var VoiceOver_Chat_RecordModeVoiceMessage: String { return self._s[3765]! } - public var Wallet_Settings_Configuration: String { return self._s[3766]! } - public var GroupInfo_InviteByLink: String { return self._s[3767]! } - public var MessageTimer_Custom: String { return self._s[3768]! } - public var UserInfo_NotificationsDefaultEnabled: String { return self._s[3769]! } - public var Conversation_StopQuizConfirmationTitle: String { return self._s[3770]! } - public var Passport_Address_TypeTemporaryRegistration: String { return self._s[3772]! } - public var Conversation_SendMessage_SetReminder: String { return self._s[3773]! } - public var VoiceOver_Chat_Selected: String { return self._s[3774]! } - public var ChatSettings_AutoDownloadUsingWiFi: String { return self._s[3775]! } - public var Channel_Username_InvalidTaken: String { return self._s[3776]! } - public var Conversation_ClousStorageInfo_Description3: String { return self._s[3777]! } - public var Wallet_WordCheck_TryAgain: String { return self._s[3778]! } - public var Wallet_Info_TransactionPendingHeader: String { return self._s[3779]! } - public var Settings_ChatBackground: String { return self._s[3780]! } - public var Channel_Subscribers_Title: String { return self._s[3781]! } - public var Wallet_Receive_InvoiceUrlHeader: String { return self._s[3782]! } - public var ApplyLanguage_ChangeLanguageTitle: String { return self._s[3783]! } - public var Watch_ConnectionDescription: String { return self._s[3784]! } - public var OldChannels_NoticeText: String { return self._s[3787]! } - public var Wallet_Configuration_ApplyErrorTitle: String { return self._s[3788]! } - public var IntentsSettings_SuggestBy: String { return self._s[3790]! } - public var Theme_ThemeChangedText: String { return self._s[3791]! } - public var ChatList_ArchivedChatsTitle: String { return self._s[3792]! } - public var Wallpaper_ResetWallpapers: String { return self._s[3793]! } - public var Wallet_Send_TransactionInProgress: String { return self._s[3794]! } - public var EditProfile_Title: String { return self._s[3795]! } - public var NotificationsSound_Bamboo: String { return self._s[3797]! } - public var Channel_AdminLog_MessagePreviousMessage: String { return self._s[3799]! } - public var Login_SmsRequestState2: String { return self._s[3800]! } - public var Passport_Language_ar: String { return self._s[3801]! } + public var Paint_Regular: String { return self._s[3772]! } + public var ChatSettings_AutoDownloadReset: String { return self._s[3773]! } + public var SocksProxySetup_ShareLink: String { return self._s[3774]! } + public var Wallet_Qr_Title: String { return self._s[3775]! } + public var BlockedUsers_SelectUserTitle: String { return self._s[3776]! } + public var VoiceOver_Chat_RecordModeVoiceMessage: String { return self._s[3778]! } + public var Wallet_Settings_Configuration: String { return self._s[3779]! } + public var GroupInfo_InviteByLink: String { return self._s[3780]! } + public var MessageTimer_Custom: String { return self._s[3781]! } + public var UserInfo_NotificationsDefaultEnabled: String { return self._s[3782]! } + public var Conversation_StopQuizConfirmationTitle: String { return self._s[3783]! } + public var Passport_Address_TypeTemporaryRegistration: String { return self._s[3785]! } + public var Conversation_SendMessage_SetReminder: String { return self._s[3786]! } + public var VoiceOver_Chat_Selected: String { return self._s[3787]! } + public var ChatSettings_AutoDownloadUsingWiFi: String { return self._s[3788]! } + public var Channel_Username_InvalidTaken: String { return self._s[3789]! } + public var Conversation_ClousStorageInfo_Description3: String { return self._s[3790]! } + public var Wallet_WordCheck_TryAgain: String { return self._s[3791]! } + public var Wallet_Info_TransactionPendingHeader: String { return self._s[3792]! } + public var Settings_ChatBackground: String { return self._s[3793]! } + public var Channel_Subscribers_Title: String { return self._s[3794]! } + public var Wallet_Receive_InvoiceUrlHeader: String { return self._s[3795]! } + public var ApplyLanguage_ChangeLanguageTitle: String { return self._s[3796]! } + public var Watch_ConnectionDescription: String { return self._s[3797]! } + public var OldChannels_NoticeText: String { return self._s[3800]! } + public var Wallet_Configuration_ApplyErrorTitle: String { return self._s[3801]! } + public var IntentsSettings_SuggestBy: String { return self._s[3803]! } + public var Theme_ThemeChangedText: String { return self._s[3804]! } + public var ChatList_ArchivedChatsTitle: String { return self._s[3805]! } + public var Wallpaper_ResetWallpapers: String { return self._s[3806]! } + public var Wallet_Send_TransactionInProgress: String { return self._s[3807]! } + public var EditProfile_Title: String { return self._s[3808]! } + public var NotificationsSound_Bamboo: String { return self._s[3810]! } + public var Channel_AdminLog_MessagePreviousMessage: String { return self._s[3812]! } + public var Login_SmsRequestState2: String { return self._s[3813]! } + public var Passport_Language_ar: String { return self._s[3814]! } public func Message_AuthorPinnedGame(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3802]!, self._r[3802]!, [_0]) + return formatWithArgumentRanges(self._s[3815]!, self._r[3815]!, [_0]) } - public var SettingsSearch_Synonyms_EditProfile_Title: String { return self._s[3803]! } - public var Wallet_Created_Text: String { return self._s[3804]! } - public var Conversation_MessageDialogEdit: String { return self._s[3806]! } - public var Wallet_Created_Proceed: String { return self._s[3807]! } - public var Wallet_Words_Done: String { return self._s[3808]! } - public var VoiceOver_Media_PlaybackPause: String { return self._s[3809]! } + public var SettingsSearch_Synonyms_EditProfile_Title: String { return self._s[3816]! } + public var Wallet_Created_Text: String { return self._s[3817]! } + public var Conversation_MessageDialogEdit: String { return self._s[3819]! } + public var Wallet_Created_Proceed: String { return self._s[3820]! } + public var Wallet_Words_Done: String { return self._s[3821]! } + public var VoiceOver_Media_PlaybackPause: String { return self._s[3822]! } public func PUSH_AUTH_UNKNOWN(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3810]!, self._r[3810]!, [_1]) + return formatWithArgumentRanges(self._s[3823]!, self._r[3823]!, [_1]) } - public var Common_Close: String { return self._s[3811]! } - public var GroupInfo_PublicLink: String { return self._s[3812]! } - public var Channel_OwnershipTransfer_ErrorPrivacyRestricted: String { return self._s[3813]! } - public var SettingsSearch_Synonyms_Notifications_GroupNotificationsPreview: String { return self._s[3814]! } + public var Common_Close: String { return self._s[3824]! } + public var GroupInfo_PublicLink: String { return self._s[3825]! } + public var Channel_OwnershipTransfer_ErrorPrivacyRestricted: String { return self._s[3826]! } + public var SettingsSearch_Synonyms_Notifications_GroupNotificationsPreview: String { return self._s[3827]! } public func Channel_AdminLog_MessageToggleInvitesOff(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3818]!, self._r[3818]!, [_0]) + return formatWithArgumentRanges(self._s[3831]!, self._r[3831]!, [_0]) } - public var UserInfo_About_Placeholder: String { return self._s[3819]! } + public var UserInfo_About_Placeholder: String { return self._s[3832]! } public func Conversation_FileHowToText(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3820]!, self._r[3820]!, [_0]) + return formatWithArgumentRanges(self._s[3833]!, self._r[3833]!, [_0]) } - public var GroupInfo_Permissions_SectionTitle: String { return self._s[3821]! } - public var Channel_Info_Banned: String { return self._s[3823]! } + public var GroupInfo_Permissions_SectionTitle: String { return self._s[3834]! } + public var Channel_Info_Banned: String { return self._s[3836]! } public func Time_MonthOfYear_m11(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3824]!, self._r[3824]!, [_0]) + return formatWithArgumentRanges(self._s[3837]!, self._r[3837]!, [_0]) } - public var Appearance_Other: String { return self._s[3825]! } - public var Passport_Language_my: String { return self._s[3826]! } - public var Group_Setup_BasicHistoryHiddenHelp: String { return self._s[3827]! } + public var Appearance_Other: String { return self._s[3838]! } + public var Passport_Language_my: String { return self._s[3839]! } + public var Group_Setup_BasicHistoryHiddenHelp: String { return self._s[3840]! } public func Time_PreciseDate_m9(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3828]!, self._r[3828]!, [_1, _2, _3]) + return formatWithArgumentRanges(self._s[3841]!, self._r[3841]!, [_1, _2, _3]) } - public var SettingsSearch_Synonyms_Privacy_PasscodeAndFaceId: String { return self._s[3829]! } - public var IntentsSettings_SuggestedAndSpotlightChatsInfo: String { return self._s[3830]! } - public var Preview_CopyAddress: String { return self._s[3831]! } + public var SettingsSearch_Synonyms_Privacy_PasscodeAndFaceId: String { return self._s[3842]! } + public var IntentsSettings_SuggestedAndSpotlightChatsInfo: String { return self._s[3843]! } + public var Preview_CopyAddress: String { return self._s[3844]! } public func DialogList_SinglePlayingGameSuffix(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3832]!, self._r[3832]!, [_0]) - } - public var KeyCommand_JumpToPreviousChat: String { return self._s[3833]! } - public var UserInfo_BotSettings: String { return self._s[3834]! } - public var LiveLocation_MenuStopAll: String { return self._s[3836]! } - public var Passport_PasswordCreate: String { return self._s[3837]! } - public var StickerSettings_MaskContextInfo: String { return self._s[3838]! } - public var Message_PinnedLocationMessage: String { return self._s[3839]! } - public var Map_Satellite: String { return self._s[3840]! } - public var Watch_Message_Unsupported: String { return self._s[3841]! } - public var Username_TooManyPublicUsernamesError: String { return self._s[3842]! } - public var TwoStepAuth_EnterPasswordInvalid: String { return self._s[3843]! } - public func Notification_PinnedTextMessage(_ _0: String, _ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3844]!, self._r[3844]!, [_0, _1]) - } - public func Conversation_OpenBotLinkText(_ _0: String) -> (String, [(Int, NSRange)]) { return formatWithArgumentRanges(self._s[3845]!, self._r[3845]!, [_0]) } - public var Wallet_WordImport_Continue: String { return self._s[3846]! } + public var KeyCommand_JumpToPreviousChat: String { return self._s[3846]! } + public var UserInfo_BotSettings: String { return self._s[3847]! } + public var LiveLocation_MenuStopAll: String { return self._s[3849]! } + public var Passport_PasswordCreate: String { return self._s[3850]! } + public var StickerSettings_MaskContextInfo: String { return self._s[3851]! } + public var Message_PinnedLocationMessage: String { return self._s[3852]! } + public var Map_Satellite: String { return self._s[3853]! } + public var Watch_Message_Unsupported: String { return self._s[3854]! } + public var Username_TooManyPublicUsernamesError: String { return self._s[3855]! } + public var TwoStepAuth_EnterPasswordInvalid: String { return self._s[3856]! } + public func Notification_PinnedTextMessage(_ _0: String, _ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3857]!, self._r[3857]!, [_0, _1]) + } + public func Conversation_OpenBotLinkText(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3858]!, self._r[3858]!, [_0]) + } + public var Wallet_WordImport_Continue: String { return self._s[3859]! } public func TwoFactorSetup_EmailVerification_Text(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3847]!, self._r[3847]!, [_0]) + return formatWithArgumentRanges(self._s[3860]!, self._r[3860]!, [_0]) } - public var Notifications_ChannelNotificationsHelp: String { return self._s[3848]! } - public var Privacy_Calls_P2PContacts: String { return self._s[3849]! } - public var NotificationsSound_None: String { return self._s[3850]! } - public var Wallet_TransactionInfo_StorageFeeHeader: String { return self._s[3851]! } - public var Channel_DiscussionGroup_UnlinkGroup: String { return self._s[3853]! } - public var AccessDenied_VoiceMicrophone: String { return self._s[3854]! } + public var Notifications_ChannelNotificationsHelp: String { return self._s[3861]! } + public var Privacy_Calls_P2PContacts: String { return self._s[3862]! } + public var NotificationsSound_None: String { return self._s[3863]! } + public var Wallet_TransactionInfo_StorageFeeHeader: String { return self._s[3864]! } + public var Channel_DiscussionGroup_UnlinkGroup: String { return self._s[3866]! } + public var AccessDenied_VoiceMicrophone: String { return self._s[3867]! } public func ApplyLanguage_ChangeLanguageAlreadyActive(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3855]!, self._r[3855]!, [_1]) + return formatWithArgumentRanges(self._s[3868]!, self._r[3868]!, [_1]) } - public var Cache_Indexing: String { return self._s[3856]! } - public var DialogList_RecentTitlePeople: String { return self._s[3858]! } - public var DialogList_EncryptionRejected: String { return self._s[3859]! } - public var GroupInfo_Administrators: String { return self._s[3860]! } - public var Passport_ScanPassportHelp: String { return self._s[3861]! } - public var Application_Name: String { return self._s[3862]! } - public var Channel_AdminLogFilter_ChannelEventsInfo: String { return self._s[3863]! } - public var PeopleNearby_MakeVisible: String { return self._s[3865]! } - public var Appearance_ThemeCarouselDay: String { return self._s[3866]! } - public var Passport_Identity_TranslationHelp: String { return self._s[3867]! } + public var Cache_Indexing: String { return self._s[3869]! } + public var DialogList_RecentTitlePeople: String { return self._s[3871]! } + public var DialogList_EncryptionRejected: String { return self._s[3872]! } + public var GroupInfo_Administrators: String { return self._s[3873]! } + public var Passport_ScanPassportHelp: String { return self._s[3874]! } + public var Application_Name: String { return self._s[3875]! } + public var Channel_AdminLogFilter_ChannelEventsInfo: String { return self._s[3876]! } + public var PeopleNearby_MakeVisible: String { return self._s[3878]! } + public var Appearance_ThemeCarouselDay: String { return self._s[3879]! } + public var Stats_GrowthTitle: String { return self._s[3880]! } + public var Passport_Identity_TranslationHelp: String { return self._s[3881]! } public func VoiceOver_Chat_VideoMessageFrom(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3868]!, self._r[3868]!, [_0]) + return formatWithArgumentRanges(self._s[3882]!, self._r[3882]!, [_0]) } public func Notification_JoinedGroupByLink(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3869]!, self._r[3869]!, [_0]) + return formatWithArgumentRanges(self._s[3883]!, self._r[3883]!, [_0]) } public func DialogList_EncryptedChatStartedOutgoing(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3870]!, self._r[3870]!, [_0]) + return formatWithArgumentRanges(self._s[3884]!, self._r[3884]!, [_0]) } - public var Channel_EditAdmin_PermissionDeleteMessages: String { return self._s[3871]! } - public var Privacy_ChatsTitle: String { return self._s[3872]! } - public var DialogList_ClearHistoryConfirmation: String { return self._s[3873]! } - public var SettingsSearch_Synonyms_Data_Storage_ClearCache: String { return self._s[3874]! } - public var Watch_Suggestion_HoldOn: String { return self._s[3875]! } - public var Group_EditAdmin_TransferOwnership: String { return self._s[3876]! } - public var WebBrowser_Title: String { return self._s[3877]! } - public var Group_LinkedChannel: String { return self._s[3878]! } - public var VoiceOver_Chat_SeenByRecipient: String { return self._s[3879]! } - public var SocksProxySetup_RequiredCredentials: String { return self._s[3880]! } - public var Passport_Address_TypeRentalAgreementUploadScan: String { return self._s[3881]! } - public var Appearance_TextSize_UseSystem: String { return self._s[3882]! } - public var TwoStepAuth_EmailSkipAlert: String { return self._s[3883]! } - public var ScheduledMessages_RemindersTitle: String { return self._s[3885]! } - public var Channel_Setup_TypePublic: String { return self._s[3887]! } + public var Channel_EditAdmin_PermissionDeleteMessages: String { return self._s[3885]! } + public var Privacy_ChatsTitle: String { return self._s[3886]! } + public var DialogList_ClearHistoryConfirmation: String { return self._s[3887]! } + public var SettingsSearch_Synonyms_Data_Storage_ClearCache: String { return self._s[3888]! } + public var Watch_Suggestion_HoldOn: String { return self._s[3889]! } + public var Group_EditAdmin_TransferOwnership: String { return self._s[3890]! } + public var WebBrowser_Title: String { return self._s[3891]! } + public var Group_LinkedChannel: String { return self._s[3892]! } + public var VoiceOver_Chat_SeenByRecipient: String { return self._s[3893]! } + public var SocksProxySetup_RequiredCredentials: String { return self._s[3894]! } + public var Passport_Address_TypeRentalAgreementUploadScan: String { return self._s[3895]! } + public var Appearance_TextSize_UseSystem: String { return self._s[3896]! } + public var TwoStepAuth_EmailSkipAlert: String { return self._s[3897]! } + public var ScheduledMessages_RemindersTitle: String { return self._s[3899]! } + public var Channel_Setup_TypePublic: String { return self._s[3901]! } public func Channel_AdminLog_MessageToggleInvitesOn(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3888]!, self._r[3888]!, [_0]) + return formatWithArgumentRanges(self._s[3902]!, self._r[3902]!, [_0]) } - public var Channel_TypeSetup_Title: String { return self._s[3890]! } - public var MessagePoll_ViewResults: String { return self._s[3891]! } - public var Map_OpenInMaps: String { return self._s[3893]! } + public var Channel_TypeSetup_Title: String { return self._s[3904]! } + public var MessagePoll_ViewResults: String { return self._s[3905]! } + public var Map_OpenInMaps: String { return self._s[3907]! } public func PUSH_PINNED_NOTEXT(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3894]!, self._r[3894]!, [_1]) + return formatWithArgumentRanges(self._s[3908]!, self._r[3908]!, [_1]) } - public var NotificationsSound_Tremolo: String { return self._s[3896]! } + public var NotificationsSound_Tremolo: String { return self._s[3910]! } public func Date_ChatDateHeaderYear(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3897]!, self._r[3897]!, [_1, _2, _3]) + return formatWithArgumentRanges(self._s[3911]!, self._r[3911]!, [_1, _2, _3]) } - public var ConversationProfile_UnknownAddMemberError: String { return self._s[3898]! } - public var Channel_OwnershipTransfer_PasswordPlaceholder: String { return self._s[3899]! } - public var Passport_PasswordHelp: String { return self._s[3900]! } - public var Login_CodeExpiredError: String { return self._s[3901]! } - public var Channel_EditAdmin_PermissionChangeInfo: String { return self._s[3902]! } - public var Conversation_TitleUnmute: String { return self._s[3903]! } - public var Passport_Identity_ScansHelp: String { return self._s[3904]! } - public var Passport_Language_lo: String { return self._s[3905]! } - public var Camera_FlashAuto: String { return self._s[3906]! } - public var Conversation_OpenBotLinkOpen: String { return self._s[3907]! } - public var Common_Cancel: String { return self._s[3908]! } - public var DialogList_SavedMessagesTooltip: String { return self._s[3909]! } - public var TwoStepAuth_SetupPasswordTitle: String { return self._s[3910]! } - public var Appearance_TintAllColors: String { return self._s[3911]! } + public var ConversationProfile_UnknownAddMemberError: String { return self._s[3912]! } + public var Channel_OwnershipTransfer_PasswordPlaceholder: String { return self._s[3913]! } + public var Passport_PasswordHelp: String { return self._s[3914]! } + public var Login_CodeExpiredError: String { return self._s[3915]! } + public var Channel_EditAdmin_PermissionChangeInfo: String { return self._s[3916]! } + public var Conversation_TitleUnmute: String { return self._s[3917]! } + public var Passport_Identity_ScansHelp: String { return self._s[3918]! } + public var Passport_Language_lo: String { return self._s[3919]! } + public var Camera_FlashAuto: String { return self._s[3920]! } + public var Conversation_OpenBotLinkOpen: String { return self._s[3921]! } + public var Common_Cancel: String { return self._s[3922]! } + public var DialogList_SavedMessagesTooltip: String { return self._s[3923]! } + public var TwoStepAuth_SetupPasswordTitle: String { return self._s[3924]! } + public var Appearance_TintAllColors: String { return self._s[3925]! } public func PUSH_MESSAGE_FWD(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3912]!, self._r[3912]!, [_1]) + return formatWithArgumentRanges(self._s[3926]!, self._r[3926]!, [_1]) } - public var Conversation_ReportSpamConfirmation: String { return self._s[3913]! } - public var ChatSettings_Title: String { return self._s[3915]! } - public var Passport_PasswordReset: String { return self._s[3916]! } - public var SocksProxySetup_TypeNone: String { return self._s[3917]! } - public var EditTheme_Title: String { return self._s[3920]! } - public var PhoneNumberHelp_Help: String { return self._s[3921]! } - public var Checkout_EnterPassword: String { return self._s[3922]! } - public var Activity_UploadingDocument: String { return self._s[3924]! } - public var Share_AuthTitle: String { return self._s[3925]! } - public var State_Connecting: String { return self._s[3926]! } - public var Profile_MessageLifetime1w: String { return self._s[3927]! } - public var Conversation_ContextMenuReport: String { return self._s[3928]! } - public var CheckoutInfo_ReceiverInfoPhone: String { return self._s[3929]! } - public var AutoNightTheme_ScheduledTo: String { return self._s[3930]! } + public var Conversation_ReportSpamConfirmation: String { return self._s[3927]! } + public var ChatSettings_Title: String { return self._s[3929]! } + public var Passport_PasswordReset: String { return self._s[3930]! } + public var SocksProxySetup_TypeNone: String { return self._s[3931]! } + public var EditTheme_Title: String { return self._s[3934]! } + public var PhoneNumberHelp_Help: String { return self._s[3935]! } + public var Checkout_EnterPassword: String { return self._s[3936]! } + public var Activity_UploadingDocument: String { return self._s[3938]! } + public var Share_AuthTitle: String { return self._s[3939]! } + public var State_Connecting: String { return self._s[3940]! } + public var Profile_MessageLifetime1w: String { return self._s[3941]! } + public var Conversation_ContextMenuReport: String { return self._s[3942]! } + public var CheckoutInfo_ReceiverInfoPhone: String { return self._s[3943]! } + public var AutoNightTheme_ScheduledTo: String { return self._s[3944]! } public func VoiceOver_Chat_AnonymousPollFrom(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3931]!, self._r[3931]!, [_0]) + return formatWithArgumentRanges(self._s[3945]!, self._r[3945]!, [_0]) } - public var AuthSessions_Terminate: String { return self._s[3932]! } - public var Wallet_WordImport_CanNotRemember: String { return self._s[3933]! } - public var PeerInfo_PaneAudio: String { return self._s[3934]! } - public var Checkout_NewCard_CardholderNamePlaceholder: String { return self._s[3936]! } - public var KeyCommand_JumpToPreviousUnreadChat: String { return self._s[3937]! } - public var PhotoEditor_Set: String { return self._s[3938]! } - public var EmptyGroupInfo_Title: String { return self._s[3939]! } - public var Login_PadPhoneHelp: String { return self._s[3940]! } - public var AutoDownloadSettings_TypeGroupChats: String { return self._s[3942]! } - public var PrivacyPolicy_DeclineLastWarning: String { return self._s[3944]! } - public var NotificationsSound_Complete: String { return self._s[3945]! } - public var SettingsSearch_Synonyms_Privacy_Data_Title: String { return self._s[3946]! } - public var Group_Info_AdminLog: String { return self._s[3947]! } - public var GroupPermission_NotAvailableInPublicGroups: String { return self._s[3948]! } + public var AuthSessions_Terminate: String { return self._s[3946]! } + public var Wallet_WordImport_CanNotRemember: String { return self._s[3947]! } + public var PeerInfo_PaneAudio: String { return self._s[3948]! } + public var Checkout_NewCard_CardholderNamePlaceholder: String { return self._s[3950]! } + public var KeyCommand_JumpToPreviousUnreadChat: String { return self._s[3951]! } + public var PhotoEditor_Set: String { return self._s[3952]! } + public var EmptyGroupInfo_Title: String { return self._s[3953]! } + public var Login_PadPhoneHelp: String { return self._s[3954]! } + public var AutoDownloadSettings_TypeGroupChats: String { return self._s[3956]! } + public var PrivacyPolicy_DeclineLastWarning: String { return self._s[3958]! } + public var NotificationsSound_Complete: String { return self._s[3959]! } + public var SettingsSearch_Synonyms_Privacy_Data_Title: String { return self._s[3960]! } + public var Group_Info_AdminLog: String { return self._s[3961]! } + public var GroupPermission_NotAvailableInPublicGroups: String { return self._s[3962]! } public func Wallet_Time_PreciseDate_m11(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3949]!, self._r[3949]!, [_1, _2, _3]) + return formatWithArgumentRanges(self._s[3963]!, self._r[3963]!, [_1, _2, _3]) } - public var Channel_AdminLog_InfoPanelAlertText: String { return self._s[3950]! } - public var Group_Location_CreateInThisPlace: String { return self._s[3952]! } - public var Conversation_Admin: String { return self._s[3953]! } - public var Conversation_GifTooltip: String { return self._s[3954]! } - public var Passport_NotLoggedInMessage: String { return self._s[3955]! } + public var Channel_AdminLog_InfoPanelAlertText: String { return self._s[3964]! } + public var Group_Location_CreateInThisPlace: String { return self._s[3966]! } + public var Conversation_Admin: String { return self._s[3967]! } + public var Conversation_GifTooltip: String { return self._s[3968]! } + public var Passport_NotLoggedInMessage: String { return self._s[3969]! } public func AutoDownloadSettings_OnFor(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3957]!, self._r[3957]!, [_0]) + return formatWithArgumentRanges(self._s[3971]!, self._r[3971]!, [_0]) } - public var Profile_MessageLifetimeForever: String { return self._s[3958]! } - public var SharedMedia_EmptyTitle: String { return self._s[3960]! } - public var Channel_Edit_PrivatePublicLinkAlert: String { return self._s[3962]! } - public var Username_Help: String { return self._s[3963]! } - public var DialogList_LanguageTooltip: String { return self._s[3965]! } - public var Map_LoadError: String { return self._s[3966]! } - public var Login_PhoneNumberAlreadyAuthorized: String { return self._s[3967]! } - public var Channel_AdminLog_AddMembers: String { return self._s[3968]! } - public var ArchivedChats_IntroTitle2: String { return self._s[3969]! } - public var Notification_Exceptions_NewException: String { return self._s[3970]! } - public var TwoStepAuth_EmailTitle: String { return self._s[3971]! } - public var WatchRemote_AlertText: String { return self._s[3972]! } + public var Profile_MessageLifetimeForever: String { return self._s[3972]! } + public var SharedMedia_EmptyTitle: String { return self._s[3974]! } + public var Channel_Edit_PrivatePublicLinkAlert: String { return self._s[3976]! } + public var Username_Help: String { return self._s[3977]! } + public var DialogList_LanguageTooltip: String { return self._s[3979]! } + public var Map_LoadError: String { return self._s[3980]! } + public var Login_PhoneNumberAlreadyAuthorized: String { return self._s[3981]! } + public var Channel_AdminLog_AddMembers: String { return self._s[3982]! } + public var ArchivedChats_IntroTitle2: String { return self._s[3983]! } + public var Notification_Exceptions_NewException: String { return self._s[3984]! } + public var TwoStepAuth_EmailTitle: String { return self._s[3985]! } + public var WatchRemote_AlertText: String { return self._s[3986]! } public func Wallet_Send_ConfirmationText(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3973]!, self._r[3973]!, [_1, _2, _3]) + return formatWithArgumentRanges(self._s[3987]!, self._r[3987]!, [_1, _2, _3]) } - public var ChatSettings_ConnectionType_Title: String { return self._s[3977]! } + public var ChatSettings_ConnectionType_Title: String { return self._s[3991]! } public func PUSH_PINNED_QUIZ(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3978]!, self._r[3978]!, [_1, _2]) + return formatWithArgumentRanges(self._s[3992]!, self._r[3992]!, [_1, _2]) } public func Settings_CheckPhoneNumberTitle(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3979]!, self._r[3979]!, [_0]) + return formatWithArgumentRanges(self._s[3993]!, self._r[3993]!, [_0]) } - public var SettingsSearch_Synonyms_Calls_CallTab: String { return self._s[3980]! } - public var WebBrowser_DefaultBrowser: String { return self._s[3981]! } - public var Passport_Address_CountryPlaceholder: String { return self._s[3982]! } + public var SettingsSearch_Synonyms_Calls_CallTab: String { return self._s[3994]! } + public var WebBrowser_DefaultBrowser: String { return self._s[3995]! } + public var Passport_Address_CountryPlaceholder: String { return self._s[3996]! } public func DialogList_AwaitingEncryption(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3983]!, self._r[3983]!, [_0]) + return formatWithArgumentRanges(self._s[3997]!, self._r[3997]!, [_0]) } public func Time_PreciseDate_m6(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3984]!, self._r[3984]!, [_1, _2, _3]) + return formatWithArgumentRanges(self._s[3998]!, self._r[3998]!, [_1, _2, _3]) } - public var Group_AdminLog_EmptyText: String { return self._s[3985]! } - public var SettingsSearch_Synonyms_Appearance_Title: String { return self._s[3986]! } - public var Conversation_PrivateChannelTooltip: String { return self._s[3988]! } - public var Wallet_Created_ExportErrorText: String { return self._s[3989]! } - public var ChatList_UndoArchiveText1: String { return self._s[3990]! } - public var AccessDenied_VideoMicrophone: String { return self._s[3991]! } - public var Conversation_ContextMenuStickerPackAdd: String { return self._s[3992]! } - public var Cache_ClearNone: String { return self._s[3993]! } - public var SocksProxySetup_FailedToConnect: String { return self._s[3994]! } - public var Permissions_NotificationsTitle_v0: String { return self._s[3995]! } + public var Group_AdminLog_EmptyText: String { return self._s[3999]! } + public var SettingsSearch_Synonyms_Appearance_Title: String { return self._s[4000]! } + public var Conversation_PrivateChannelTooltip: String { return self._s[4002]! } + public var Wallet_Created_ExportErrorText: String { return self._s[4003]! } + public var ChatList_UndoArchiveText1: String { return self._s[4004]! } + public var AccessDenied_VideoMicrophone: String { return self._s[4005]! } + public var Conversation_ContextMenuStickerPackAdd: String { return self._s[4006]! } + public var Cache_ClearNone: String { return self._s[4007]! } + public var SocksProxySetup_FailedToConnect: String { return self._s[4008]! } + public var Permissions_NotificationsTitle_v0: String { return self._s[4009]! } public func Channel_AdminLog_MessageEdited(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3996]!, self._r[3996]!, [_0]) + return formatWithArgumentRanges(self._s[4010]!, self._r[4010]!, [_0]) } - public var Passport_Identity_Country: String { return self._s[3997]! } + public var Passport_Identity_Country: String { return self._s[4011]! } public func ChatSettings_AutoDownloadSettings_TypeFile(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3998]!, self._r[3998]!, [_0]) + return formatWithArgumentRanges(self._s[4012]!, self._r[4012]!, [_0]) } public func Notification_CreatedChat(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3999]!, self._r[3999]!, [_0]) + return formatWithArgumentRanges(self._s[4013]!, self._r[4013]!, [_0]) } - public var Exceptions_AddToExceptions: String { return self._s[4000]! } - public var AccessDenied_Settings: String { return self._s[4001]! } - public var Passport_Address_TypeUtilityBillUploadScan: String { return self._s[4002]! } - public var Month_ShortMay: String { return self._s[4003]! } - public var Compose_NewGroup: String { return self._s[4005]! } - public var Group_Setup_TypePrivate: String { return self._s[4007]! } - public var Login_PadPhoneHelpTitle: String { return self._s[4009]! } - public var Appearance_ThemeDayClassic: String { return self._s[4010]! } - public var Channel_AdminLog_MessagePreviousCaption: String { return self._s[4011]! } - public var AutoDownloadSettings_OffForAll: String { return self._s[4012]! } - public var Privacy_GroupsAndChannels_WhoCanAddMe: String { return self._s[4013]! } - public var Conversation_typing: String { return self._s[4015]! } - public var Undo_ScheduledMessagesCleared: String { return self._s[4016]! } - public var Paint_Masks: String { return self._s[4017]! } - public var Contacts_DeselectAll: String { return self._s[4018]! } + public var Exceptions_AddToExceptions: String { return self._s[4014]! } + public var AccessDenied_Settings: String { return self._s[4015]! } + public var Passport_Address_TypeUtilityBillUploadScan: String { return self._s[4016]! } + public var Month_ShortMay: String { return self._s[4017]! } + public var Compose_NewGroup: String { return self._s[4019]! } + public var Group_Setup_TypePrivate: String { return self._s[4021]! } + public var Login_PadPhoneHelpTitle: String { return self._s[4023]! } + public var Appearance_ThemeDayClassic: String { return self._s[4024]! } + public var Channel_AdminLog_MessagePreviousCaption: String { return self._s[4025]! } + public var AutoDownloadSettings_OffForAll: String { return self._s[4026]! } + public var Privacy_GroupsAndChannels_WhoCanAddMe: String { return self._s[4027]! } + public var Conversation_typing: String { return self._s[4029]! } + public var Undo_ScheduledMessagesCleared: String { return self._s[4030]! } + public var Paint_Masks: String { return self._s[4031]! } + public var Contacts_DeselectAll: String { return self._s[4032]! } public func Wallet_Updated_YesterdayAt(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4019]!, self._r[4019]!, [_0]) + return formatWithArgumentRanges(self._s[4033]!, self._r[4033]!, [_0]) } - public var CreatePoll_MultipleChoiceQuizAlert: String { return self._s[4020]! } - public var Username_InvalidTaken: String { return self._s[4021]! } - public var Call_StatusNoAnswer: String { return self._s[4022]! } - public var TwoStepAuth_EmailAddSuccess: String { return self._s[4023]! } - public var SettingsSearch_Synonyms_Privacy_BlockedUsers: String { return self._s[4024]! } - public var Passport_Identity_Selfie: String { return self._s[4025]! } - public var Login_InfoLastNamePlaceholder: String { return self._s[4026]! } - public var Privacy_SecretChatsLinkPreviewsHelp: String { return self._s[4027]! } - public var Conversation_ClearSecretHistory: String { return self._s[4028]! } - public var PeopleNearby_Description: String { return self._s[4030]! } - public var NetworkUsageSettings_Title: String { return self._s[4031]! } - public var Your_cards_security_code_is_invalid: String { return self._s[4033]! } + public var CreatePoll_MultipleChoiceQuizAlert: String { return self._s[4034]! } + public var Username_InvalidTaken: String { return self._s[4035]! } + public var Call_StatusNoAnswer: String { return self._s[4036]! } + public var TwoStepAuth_EmailAddSuccess: String { return self._s[4037]! } + public var SettingsSearch_Synonyms_Privacy_BlockedUsers: String { return self._s[4038]! } + public var Passport_Identity_Selfie: String { return self._s[4039]! } + public var Login_InfoLastNamePlaceholder: String { return self._s[4040]! } + public var Privacy_SecretChatsLinkPreviewsHelp: String { return self._s[4041]! } + public var Conversation_ClearSecretHistory: String { return self._s[4042]! } + public var PeopleNearby_Description: String { return self._s[4044]! } + public var NetworkUsageSettings_Title: String { return self._s[4045]! } + public var Your_cards_security_code_is_invalid: String { return self._s[4047]! } + public var Stats_EnabledNotifications: String { return self._s[4048]! } public func Notification_LeftChannel(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4035]!, self._r[4035]!, [_0]) + return formatWithArgumentRanges(self._s[4050]!, self._r[4050]!, [_0]) } public func Call_CallInProgressMessage(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4036]!, self._r[4036]!, [_1, _2]) + return formatWithArgumentRanges(self._s[4051]!, self._r[4051]!, [_1, _2]) } - public var SaveIncomingPhotosSettings_From: String { return self._s[4038]! } - public var VoiceOver_Navigation_Search: String { return self._s[4039]! } - public var Map_LiveLocationTitle: String { return self._s[4040]! } - public var Login_InfoAvatarAdd: String { return self._s[4041]! } - public var Passport_Identity_FilesView: String { return self._s[4042]! } - public var UserInfo_GenericPhoneLabel: String { return self._s[4043]! } - public var Privacy_Calls_NeverAllow: String { return self._s[4044]! } - public var VoiceOver_Chat_File: String { return self._s[4045]! } - public var Wallet_Settings_DeleteWalletInfo: String { return self._s[4046]! } + public var SaveIncomingPhotosSettings_From: String { return self._s[4053]! } + public var VoiceOver_Navigation_Search: String { return self._s[4054]! } + public var Map_LiveLocationTitle: String { return self._s[4055]! } + public var Login_InfoAvatarAdd: String { return self._s[4056]! } + public var Passport_Identity_FilesView: String { return self._s[4057]! } + public var UserInfo_GenericPhoneLabel: String { return self._s[4058]! } + public var Privacy_Calls_NeverAllow: String { return self._s[4059]! } + public var VoiceOver_Chat_File: String { return self._s[4060]! } + public var Wallet_Settings_DeleteWalletInfo: String { return self._s[4061]! } public func Contacts_AddPhoneNumber(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4047]!, self._r[4047]!, [_0]) - } - public var ContactInfo_PhoneNumberHidden: String { return self._s[4048]! } - public var TwoStepAuth_ConfirmationText: String { return self._s[4049]! } - public var ChatSettings_AutomaticVideoMessageDownload: String { return self._s[4050]! } - public func PUSH_CHAT_MESSAGE_VIDEOS(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4051]!, self._r[4051]!, [_1, _2, _3]) - } - public var Channel_AdminLogFilter_AdminsAll: String { return self._s[4052]! } - public var Wallet_Intro_CreateErrorText: String { return self._s[4053]! } - public var Tour_Title2: String { return self._s[4054]! } - public var Wallet_Sent_ViewWallet: String { return self._s[4055]! } - public var Conversation_FileOpenIn: String { return self._s[4056]! } - public var Checkout_ErrorPrecheckoutFailed: String { return self._s[4057]! } - public var Wallet_Send_ErrorInvalidAddress: String { return self._s[4058]! } - public var Wallpaper_Set: String { return self._s[4059]! } - public var Passport_Identity_Translations: String { return self._s[4061]! } - public func Channel_AdminLog_MessageChangedChannelAbout(_ _0: String) -> (String, [(Int, NSRange)]) { return formatWithArgumentRanges(self._s[4062]!, self._r[4062]!, [_0]) } - public var Channel_LeaveChannel: String { return self._s[4063]! } + public var ChatList_EmptyChatList: String { return self._s[4063]! } + public var ContactInfo_PhoneNumberHidden: String { return self._s[4064]! } + public var TwoStepAuth_ConfirmationText: String { return self._s[4065]! } + public var ChatSettings_AutomaticVideoMessageDownload: String { return self._s[4066]! } + public func PUSH_CHAT_MESSAGE_VIDEOS(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4067]!, self._r[4067]!, [_1, _2, _3]) + } + public var Channel_AdminLogFilter_AdminsAll: String { return self._s[4068]! } + public var Wallet_Intro_CreateErrorText: String { return self._s[4069]! } + public var Tour_Title2: String { return self._s[4070]! } + public var Wallet_Sent_ViewWallet: String { return self._s[4071]! } + public var Conversation_FileOpenIn: String { return self._s[4072]! } + public var Checkout_ErrorPrecheckoutFailed: String { return self._s[4073]! } + public var Wallet_Send_ErrorInvalidAddress: String { return self._s[4074]! } + public var Wallpaper_Set: String { return self._s[4075]! } + public var Passport_Identity_Translations: String { return self._s[4077]! } + public func Channel_AdminLog_MessageChangedChannelAbout(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4078]!, self._r[4078]!, [_0]) + } + public var Channel_LeaveChannel: String { return self._s[4079]! } public func PINNED_INVOICE(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4064]!, self._r[4064]!, [_1]) + return formatWithArgumentRanges(self._s[4080]!, self._r[4080]!, [_1]) } - public var SettingsSearch_Synonyms_Proxy_AddProxy: String { return self._s[4066]! } - public var PhotoEditor_HighlightsTint: String { return self._s[4067]! } - public var MessagePoll_LabelPoll: String { return self._s[4068]! } - public var Passport_Email_Delete: String { return self._s[4069]! } - public var Conversation_Mute: String { return self._s[4071]! } - public var Channel_AddBotAsAdmin: String { return self._s[4072]! } - public var Channel_AdminLog_CanSendMessages: String { return self._s[4074]! } - public var Wallet_Configuration_BlockchainNameChangedText: String { return self._s[4075]! } - public var ChatSettings_IntentsSettings: String { return self._s[4077]! } - public var Channel_Management_LabelOwner: String { return self._s[4078]! } + public var SettingsSearch_Synonyms_Proxy_AddProxy: String { return self._s[4082]! } + public var PhotoEditor_HighlightsTint: String { return self._s[4083]! } + public var MessagePoll_LabelPoll: String { return self._s[4084]! } + public var Passport_Email_Delete: String { return self._s[4085]! } + public var Conversation_Mute: String { return self._s[4087]! } + public var Channel_AddBotAsAdmin: String { return self._s[4088]! } + public var Channel_AdminLog_CanSendMessages: String { return self._s[4090]! } + public var Wallet_Configuration_BlockchainNameChangedText: String { return self._s[4091]! } + public var ChatSettings_IntentsSettings: String { return self._s[4093]! } + public var Channel_Management_LabelOwner: String { return self._s[4094]! } public func Notification_PassportValuesSentMessage(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4079]!, self._r[4079]!, [_1, _2]) + return formatWithArgumentRanges(self._s[4095]!, self._r[4095]!, [_1, _2]) } - public var Calls_CallTabDescription: String { return self._s[4080]! } - public var Passport_Identity_NativeNameHelp: String { return self._s[4081]! } - public var Common_No: String { return self._s[4082]! } - public var Weekday_Sunday: String { return self._s[4083]! } - public var Notification_Reply: String { return self._s[4084]! } - public var Conversation_ViewMessage: String { return self._s[4085]! } + public var Calls_CallTabDescription: String { return self._s[4096]! } + public var Passport_Identity_NativeNameHelp: String { return self._s[4097]! } + public var Common_No: String { return self._s[4098]! } + public var Weekday_Sunday: String { return self._s[4099]! } + public var Notification_Reply: String { return self._s[4100]! } + public var Conversation_ViewMessage: String { return self._s[4101]! } public func Checkout_SavePasswordTimeoutAndFaceId(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4086]!, self._r[4086]!, [_0]) + return formatWithArgumentRanges(self._s[4102]!, self._r[4102]!, [_0]) } public func Map_LiveLocationPrivateDescription(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4087]!, self._r[4087]!, [_0]) - } - public func Wallet_Time_PreciseDate_m7(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4088]!, self._r[4088]!, [_1, _2, _3]) - } - public var SettingsSearch_Synonyms_EditProfile_AddAccount: String { return self._s[4089]! } - public var Wallet_Send_Title: String { return self._s[4090]! } - public var Message_PinnedDocumentMessage: String { return self._s[4091]! } - public var Wallet_Info_RefreshErrorText: String { return self._s[4092]! } - public var DialogList_TabTitle: String { return self._s[4094]! } - public var ChatSettings_AutoPlayTitle: String { return self._s[4095]! } - public var Passport_FieldEmail: String { return self._s[4096]! } - public var Conversation_UnpinMessageAlert: String { return self._s[4097]! } - public var Passport_Address_TypeBankStatement: String { return self._s[4098]! } - public var Wallet_SecureStorageReset_Title: String { return self._s[4099]! } - public var Passport_Identity_ExpiryDate: String { return self._s[4100]! } - public var Privacy_Calls_P2P: String { return self._s[4101]! } - public func CancelResetAccount_Success(_ _0: String) -> (String, [(Int, NSRange)]) { return formatWithArgumentRanges(self._s[4103]!, self._r[4103]!, [_0]) } - public var SocksProxySetup_UseForCallsHelp: String { return self._s[4104]! } + public func Wallet_Time_PreciseDate_m7(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4104]!, self._r[4104]!, [_1, _2, _3]) + } + public var SettingsSearch_Synonyms_EditProfile_AddAccount: String { return self._s[4105]! } + public var Wallet_Send_Title: String { return self._s[4106]! } + public var Message_PinnedDocumentMessage: String { return self._s[4107]! } + public var Wallet_Info_RefreshErrorText: String { return self._s[4108]! } + public var DialogList_TabTitle: String { return self._s[4110]! } + public var ChatSettings_AutoPlayTitle: String { return self._s[4111]! } + public var Passport_FieldEmail: String { return self._s[4112]! } + public var Conversation_UnpinMessageAlert: String { return self._s[4113]! } + public var Passport_Address_TypeBankStatement: String { return self._s[4114]! } + public var Wallet_SecureStorageReset_Title: String { return self._s[4115]! } + public var Passport_Identity_ExpiryDate: String { return self._s[4116]! } + public var Privacy_Calls_P2P: String { return self._s[4117]! } + public func CancelResetAccount_Success(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4119]!, self._r[4119]!, [_0]) + } + public var SocksProxySetup_UseForCallsHelp: String { return self._s[4120]! } public func PUSH_CHAT_ALBUM(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4105]!, self._r[4105]!, [_1, _2]) + return formatWithArgumentRanges(self._s[4121]!, self._r[4121]!, [_1, _2]) } - public var Stickers_ClearRecent: String { return self._s[4106]! } - public var EnterPasscode_ChangeTitle: String { return self._s[4107]! } - public var TwoFactorSetup_Email_Title: String { return self._s[4108]! } - public var Passport_InfoText: String { return self._s[4109]! } - public var Checkout_NewCard_SaveInfoEnableHelp: String { return self._s[4110]! } + public var Stickers_ClearRecent: String { return self._s[4122]! } + public var EnterPasscode_ChangeTitle: String { return self._s[4123]! } + public var TwoFactorSetup_Email_Title: String { return self._s[4124]! } + public var Passport_InfoText: String { return self._s[4125]! } + public var Checkout_NewCard_SaveInfoEnableHelp: String { return self._s[4126]! } public func Login_InvalidPhoneEmailSubject(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4111]!, self._r[4111]!, [_0]) - } - public func Time_PreciseDate_m3(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4112]!, self._r[4112]!, [_1, _2, _3]) - } - public var SettingsSearch_Synonyms_Notifications_BadgeIncludeMutedChannels: String { return self._s[4113]! } - public var ScheduledMessages_PollUnavailable: String { return self._s[4114]! } - public var VoiceOver_Navigation_Compose: String { return self._s[4115]! } - public var Passport_Identity_EditDriversLicense: String { return self._s[4116]! } - public var Conversation_TapAndHoldToRecord: String { return self._s[4118]! } - public var SettingsSearch_Synonyms_Notifications_BadgeIncludeMutedChats: String { return self._s[4119]! } - public func Notification_CallTimeFormat(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4120]!, self._r[4120]!, [_1, _2]) - } - public var Channel_EditAdmin_PermissionInviteViaLink: String { return self._s[4123]! } - public var ChatSettings_OpenLinksIn: String { return self._s[4124]! } - public var Map_HomeAndWorkTitle: String { return self._s[4125]! } - public func Generic_OpenHiddenLinkAlert(_ _0: String) -> (String, [(Int, NSRange)]) { return formatWithArgumentRanges(self._s[4127]!, self._r[4127]!, [_0]) } - public var DialogList_Unread: String { return self._s[4128]! } + public func Time_PreciseDate_m3(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4128]!, self._r[4128]!, [_1, _2, _3]) + } + public var SettingsSearch_Synonyms_Notifications_BadgeIncludeMutedChannels: String { return self._s[4129]! } + public var ScheduledMessages_PollUnavailable: String { return self._s[4130]! } + public var VoiceOver_Navigation_Compose: String { return self._s[4131]! } + public var Passport_Identity_EditDriversLicense: String { return self._s[4132]! } + public var Conversation_TapAndHoldToRecord: String { return self._s[4134]! } + public var SettingsSearch_Synonyms_Notifications_BadgeIncludeMutedChats: String { return self._s[4135]! } + public func Notification_CallTimeFormat(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4136]!, self._r[4136]!, [_1, _2]) + } + public var Channel_EditAdmin_PermissionInviteViaLink: String { return self._s[4139]! } + public var ChatSettings_OpenLinksIn: String { return self._s[4140]! } + public var Map_HomeAndWorkTitle: String { return self._s[4141]! } + public func Generic_OpenHiddenLinkAlert(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4143]!, self._r[4143]!, [_0]) + } + public var DialogList_Unread: String { return self._s[4144]! } public func PUSH_CHAT_MESSAGE_GIF(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4129]!, self._r[4129]!, [_1, _2]) + return formatWithArgumentRanges(self._s[4145]!, self._r[4145]!, [_1, _2]) } - public var User_DeletedAccount: String { return self._s[4130]! } - public var OwnershipTransfer_SetupTwoStepAuth: String { return self._s[4131]! } + public var User_DeletedAccount: String { return self._s[4146]! } + public var OwnershipTransfer_SetupTwoStepAuth: String { return self._s[4147]! } public func Watch_Time_ShortYesterdayAt(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4132]!, self._r[4132]!, [_0]) + return formatWithArgumentRanges(self._s[4148]!, self._r[4148]!, [_0]) } - public var UserInfo_NotificationsDefault: String { return self._s[4133]! } - public var SharedMedia_CategoryMedia: String { return self._s[4134]! } - public var SocksProxySetup_ProxyStatusUnavailable: String { return self._s[4135]! } - public var Channel_AdminLog_MessageRestrictedForever: String { return self._s[4136]! } - public var Watch_ChatList_Compose: String { return self._s[4137]! } - public var Notifications_MessageNotificationsExceptionsHelp: String { return self._s[4138]! } - public var AutoDownloadSettings_Delimeter: String { return self._s[4139]! } - public var Watch_Microphone_Access: String { return self._s[4140]! } - public var Group_Setup_HistoryHeader: String { return self._s[4141]! } - public var Map_SetThisLocation: String { return self._s[4142]! } - public var Appearance_ThemePreview_Chat_2_ReplyName: String { return self._s[4143]! } - public var Activity_UploadingPhoto: String { return self._s[4144]! } - public var Conversation_Edit: String { return self._s[4146]! } - public var Group_ErrorSendRestrictedMedia: String { return self._s[4147]! } - public var Login_TermsOfServiceDecline: String { return self._s[4148]! } - public var Message_PinnedContactMessage: String { return self._s[4149]! } + public var UserInfo_NotificationsDefault: String { return self._s[4149]! } + public var SharedMedia_CategoryMedia: String { return self._s[4150]! } + public var SocksProxySetup_ProxyStatusUnavailable: String { return self._s[4151]! } + public var Channel_AdminLog_MessageRestrictedForever: String { return self._s[4152]! } + public var Watch_ChatList_Compose: String { return self._s[4153]! } + public var Notifications_MessageNotificationsExceptionsHelp: String { return self._s[4154]! } + public var AutoDownloadSettings_Delimeter: String { return self._s[4155]! } + public var Watch_Microphone_Access: String { return self._s[4156]! } + public var Group_Setup_HistoryHeader: String { return self._s[4157]! } + public var Map_SetThisLocation: String { return self._s[4158]! } + public var Appearance_ThemePreview_Chat_2_ReplyName: String { return self._s[4159]! } + public var Activity_UploadingPhoto: String { return self._s[4160]! } + public var Conversation_Edit: String { return self._s[4162]! } + public var Group_ErrorSendRestrictedMedia: String { return self._s[4163]! } + public var Login_TermsOfServiceDecline: String { return self._s[4164]! } + public var Message_PinnedContactMessage: String { return self._s[4165]! } public func Channel_AdminLog_MessageRestrictedNameUsername(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4150]!, self._r[4150]!, [_1, _2]) + return formatWithArgumentRanges(self._s[4166]!, self._r[4166]!, [_1, _2]) } public func Login_PhoneBannedEmailBody(_ _1: String, _ _2: String, _ _3: String, _ _4: String, _ _5: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4151]!, self._r[4151]!, [_1, _2, _3, _4, _5]) + return formatWithArgumentRanges(self._s[4167]!, self._r[4167]!, [_1, _2, _3, _4, _5]) } - public var Appearance_LargeEmoji: String { return self._s[4152]! } - public var TwoStepAuth_AdditionalPassword: String { return self._s[4154]! } - public var EditTheme_Edit_Preview_IncomingReplyText: String { return self._s[4155]! } + public var Appearance_LargeEmoji: String { return self._s[4168]! } + public var TwoStepAuth_AdditionalPassword: String { return self._s[4170]! } + public var EditTheme_Edit_Preview_IncomingReplyText: String { return self._s[4171]! } public func PUSH_CHAT_DELETE_YOU(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4156]!, self._r[4156]!, [_1, _2]) + return formatWithArgumentRanges(self._s[4172]!, self._r[4172]!, [_1, _2]) } - public var Passport_Phone_EnterOtherNumber: String { return self._s[4157]! } - public var Message_PinnedPhotoMessage: String { return self._s[4158]! } - public var Passport_FieldPhone: String { return self._s[4159]! } - public var TwoStepAuth_RecoveryEmailAddDescription: String { return self._s[4160]! } - public var ChatSettings_AutoPlayGifs: String { return self._s[4161]! } - public var InfoPlist_NSCameraUsageDescription: String { return self._s[4163]! } - public var Conversation_Call: String { return self._s[4164]! } - public var Common_TakePhoto: String { return self._s[4166]! } - public var Group_EditAdmin_RankTitle: String { return self._s[4167]! } - public var Wallet_Receive_CommentHeader: String { return self._s[4168]! } - public var Channel_NotificationLoading: String { return self._s[4169]! } + public var Passport_Phone_EnterOtherNumber: String { return self._s[4173]! } + public var Message_PinnedPhotoMessage: String { return self._s[4174]! } + public var Passport_FieldPhone: String { return self._s[4175]! } + public var TwoStepAuth_RecoveryEmailAddDescription: String { return self._s[4176]! } + public var Stats_NotificationsTitle: String { return self._s[4177]! } + public var ChatSettings_AutoPlayGifs: String { return self._s[4178]! } + public var InfoPlist_NSCameraUsageDescription: String { return self._s[4180]! } + public var Conversation_Call: String { return self._s[4181]! } + public var Common_TakePhoto: String { return self._s[4183]! } + public var Group_EditAdmin_RankTitle: String { return self._s[4184]! } + public var Wallet_Receive_CommentHeader: String { return self._s[4185]! } + public var Channel_NotificationLoading: String { return self._s[4186]! } public func Notification_Exceptions_Sound(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4170]!, self._r[4170]!, [_0]) + return formatWithArgumentRanges(self._s[4187]!, self._r[4187]!, [_0]) } public func ScheduledMessages_ScheduledDate(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4171]!, self._r[4171]!, [_0]) + return formatWithArgumentRanges(self._s[4188]!, self._r[4188]!, [_0]) } public func PUSH_CHANNEL_MESSAGE_VIDEO(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4172]!, self._r[4172]!, [_1]) + return formatWithArgumentRanges(self._s[4189]!, self._r[4189]!, [_1]) } - public var Permissions_SiriTitle_v0: String { return self._s[4173]! } + public var Permissions_SiriTitle_v0: String { return self._s[4190]! } public func VoiceOver_Chat_VoiceMessageFrom(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4174]!, self._r[4174]!, [_0]) + return formatWithArgumentRanges(self._s[4191]!, self._r[4191]!, [_0]) } public func Login_ResetAccountProtected_Text(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4175]!, self._r[4175]!, [_0]) + return formatWithArgumentRanges(self._s[4192]!, self._r[4192]!, [_0]) } - public var Channel_MessagePhotoRemoved: String { return self._s[4176]! } - public var Wallet_Info_ReceiveGrams: String { return self._s[4177]! } - public var ClearCache_FreeSpace: String { return self._s[4178]! } - public var Appearance_BubbleCorners_Apply: String { return self._s[4179]! } - public var Common_edit: String { return self._s[4180]! } - public var PrivacySettings_AuthSessions: String { return self._s[4181]! } - public var Month_ShortJune: String { return self._s[4182]! } - public var PrivacyLastSeenSettings_AlwaysShareWith_Placeholder: String { return self._s[4183]! } - public var Call_ReportSend: String { return self._s[4184]! } - public var Watch_LastSeen_JustNow: String { return self._s[4185]! } - public var Notifications_MessageNotifications: String { return self._s[4186]! } - public var WallpaperSearch_ColorGreen: String { return self._s[4187]! } - public var BroadcastListInfo_AddRecipient: String { return self._s[4189]! } - public var Group_Status: String { return self._s[4190]! } + public var Channel_MessagePhotoRemoved: String { return self._s[4193]! } + public var Wallet_Info_ReceiveGrams: String { return self._s[4194]! } + public var ClearCache_FreeSpace: String { return self._s[4195]! } + public var Appearance_BubbleCorners_Apply: String { return self._s[4196]! } + public var Common_edit: String { return self._s[4197]! } + public var PrivacySettings_AuthSessions: String { return self._s[4198]! } + public var Month_ShortJune: String { return self._s[4199]! } + public var PrivacyLastSeenSettings_AlwaysShareWith_Placeholder: String { return self._s[4200]! } + public var Call_ReportSend: String { return self._s[4201]! } + public var Watch_LastSeen_JustNow: String { return self._s[4202]! } + public var Notifications_MessageNotifications: String { return self._s[4203]! } + public var WallpaperSearch_ColorGreen: String { return self._s[4204]! } + public var BroadcastListInfo_AddRecipient: String { return self._s[4206]! } + public var Group_Status: String { return self._s[4207]! } public func AutoNightTheme_LocationHelp(_ _0: String, _ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4191]!, self._r[4191]!, [_0, _1]) + return formatWithArgumentRanges(self._s[4208]!, self._r[4208]!, [_0, _1]) } - public var TextFormat_AddLinkTitle: String { return self._s[4192]! } - public var ShareMenu_ShareTo: String { return self._s[4193]! } - public var Conversation_Moderate_Ban: String { return self._s[4194]! } + public var TextFormat_AddLinkTitle: String { return self._s[4209]! } + public var ShareMenu_ShareTo: String { return self._s[4210]! } + public var Conversation_Moderate_Ban: String { return self._s[4211]! } public func Conversation_DeleteMessagesFor(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4195]!, self._r[4195]!, [_0]) + return formatWithArgumentRanges(self._s[4212]!, self._r[4212]!, [_0]) } - public var SharedMedia_ViewInChat: String { return self._s[4196]! } - public var Map_LiveLocationFor8Hours: String { return self._s[4197]! } + public var SharedMedia_ViewInChat: String { return self._s[4213]! } + public var Map_LiveLocationFor8Hours: String { return self._s[4214]! } public func PUSH_PINNED_PHOTO(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4198]!, self._r[4198]!, [_1]) + return formatWithArgumentRanges(self._s[4215]!, self._r[4215]!, [_1]) } public func PUSH_PINNED_POLL(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4199]!, self._r[4199]!, [_1, _2]) + return formatWithArgumentRanges(self._s[4216]!, self._r[4216]!, [_1, _2]) } public func Map_AccurateTo(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4201]!, self._r[4201]!, [_0]) + return formatWithArgumentRanges(self._s[4218]!, self._r[4218]!, [_0]) } - public var Map_OpenInHereMaps: String { return self._s[4202]! } - public var Appearance_ReduceMotion: String { return self._s[4203]! } + public var Map_OpenInHereMaps: String { return self._s[4219]! } + public var Appearance_ReduceMotion: String { return self._s[4220]! } public func PUSH_MESSAGE_TEXT(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4204]!, self._r[4204]!, [_1, _2]) + return formatWithArgumentRanges(self._s[4221]!, self._r[4221]!, [_1, _2]) } - public var Channel_Setup_TypePublicHelp: String { return self._s[4205]! } - public var Passport_Identity_EditInternalPassport: String { return self._s[4206]! } - public var PhotoEditor_Skip: String { return self._s[4207]! } - public func OldChannels_GroupFormat(_ value: Int32) -> String { + public var Channel_Setup_TypePublicHelp: String { return self._s[4222]! } + public var Passport_Identity_EditInternalPassport: String { return self._s[4223]! } + public var PhotoEditor_Skip: String { return self._s[4224]! } + public func PasscodeSettings_FailedAttempts(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) return String(format: self._ps[0 * 6 + Int(form.rawValue)]!, stringValue) } - public func MessagePoll_VotedCount(_ value: Int32) -> String { + public func MessageTimer_ShortMinutes(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) return String(format: self._ps[1 * 6 + Int(form.rawValue)]!, stringValue) } - public func QuickSend_Photos(_ value: Int32) -> String { + public func ServiceMessage_GameScoreSelfSimple(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) return String(format: self._ps[2 * 6 + Int(form.rawValue)]!, stringValue) } - public func LastSeen_HoursAgo(_ value: Int32) -> String { + public func Map_ETAHours(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) return String(format: self._ps[3 * 6 + Int(form.rawValue)]!, stringValue) } - public func Call_ShortSeconds(_ value: Int32) -> String { + public func Wallpaper_DeleteConfirmation(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) return String(format: self._ps[4 * 6 + Int(form.rawValue)]!, stringValue) } - public func Conversation_SelectedMessages(_ value: Int32) -> String { + public func Wallet_Updated_HoursAgo(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) return String(format: self._ps[5 * 6 + Int(form.rawValue)]!, stringValue) } - public func MuteExpires_Days(_ value: Int32) -> String { + public func StickerPack_StickerCount(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) return String(format: self._ps[6 * 6 + Int(form.rawValue)]!, stringValue) } - public func PUSH_MESSAGE_VIDEOS(_ selector: Int32, _ _1: String, _ _2: Int32) -> String { - let form = getPluralizationForm(self.lc, selector) - return String(format: self._ps[7 * 6 + Int(form.rawValue)]!, _1, _2) - } - public func Wallet_Updated_MinutesAgo(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[8 * 6 + Int(form.rawValue)]!, stringValue) - } - public func PrivacyLastSeenSettings_AddUsers(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[9 * 6 + Int(form.rawValue)]!, stringValue) - } - public func Notifications_ExceptionMuteExpires_Minutes(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[10 * 6 + Int(form.rawValue)]!, stringValue) - } - public func MessageTimer_ShortHours(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[11 * 6 + Int(form.rawValue)]!, stringValue) - } - public func MuteExpires_Hours(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[12 * 6 + Int(form.rawValue)]!, stringValue) - } - public func StickerPack_AddStickerCount(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[13 * 6 + Int(form.rawValue)]!, stringValue) - } - public func PUSH_MESSAGES(_ selector: Int32, _ _1: String, _ _2: Int32) -> String { - let form = getPluralizationForm(self.lc, selector) - return String(format: self._ps[14 * 6 + Int(form.rawValue)]!, _1, _2) - } - public func MessageTimer_Weeks(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[15 * 6 + Int(form.rawValue)]!, stringValue) - } - public func ForwardedVideos(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[16 * 6 + Int(form.rawValue)]!, stringValue) - } - public func Notification_GameScoreExtended(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[17 * 6 + Int(form.rawValue)]!, stringValue) - } - public func ServiceMessage_GameScoreSelfSimple(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[18 * 6 + Int(form.rawValue)]!, stringValue) - } - public func InviteText_ContactsCountText(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[19 * 6 + Int(form.rawValue)]!, stringValue) - } - public func SharedMedia_DeleteItemsConfirmation(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[20 * 6 + Int(form.rawValue)]!, stringValue) - } - public func PUSH_CHAT_MESSAGES(_ selector: Int32, _ _2: String, _ _1: String, _ _3: Int32) -> String { - let form = getPluralizationForm(self.lc, selector) - return String(format: self._ps[21 * 6 + Int(form.rawValue)]!, _2, _1, _3) - } - public func OldChannels_InactiveMonth(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[22 * 6 + Int(form.rawValue)]!, stringValue) - } - public func DialogList_LiveLocationChatsCount(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[23 * 6 + Int(form.rawValue)]!, stringValue) - } - public func ForwardedPolls(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[24 * 6 + Int(form.rawValue)]!, stringValue) - } - public func StickerPack_StickerCount(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[25 * 6 + Int(form.rawValue)]!, stringValue) - } - public func ChatList_DeleteConfirmation(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[26 * 6 + Int(form.rawValue)]!, stringValue) - } - public func Watch_LastSeen_MinutesAgo(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[27 * 6 + Int(form.rawValue)]!, stringValue) - } - public func Conversation_StatusMembers(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[28 * 6 + Int(form.rawValue)]!, stringValue) - } - public func ServiceMessage_GameScoreSimple(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[29 * 6 + Int(form.rawValue)]!, stringValue) - } - public func ForwardedGifs(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[30 * 6 + Int(form.rawValue)]!, stringValue) - } - public func Call_ShortMinutes(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[31 * 6 + Int(form.rawValue)]!, stringValue) - } - public func StickerPack_RemoveMaskCount(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[32 * 6 + Int(form.rawValue)]!, stringValue) - } - public func Chat_DeleteMessagesConfirmation(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[33 * 6 + Int(form.rawValue)]!, stringValue) - } - public func Contacts_InviteContacts(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[34 * 6 + Int(form.rawValue)]!, stringValue) - } - public func SharedMedia_Generic(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[35 * 6 + Int(form.rawValue)]!, stringValue) - } - public func GroupInfo_ShowMoreMembers(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[36 * 6 + Int(form.rawValue)]!, stringValue) - } - public func Notifications_Exceptions(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[37 * 6 + Int(form.rawValue)]!, stringValue) - } - public func PasscodeSettings_FailedAttempts(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[38 * 6 + Int(form.rawValue)]!, stringValue) - } - public func ServiceMessage_GameScoreExtended(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[39 * 6 + Int(form.rawValue)]!, stringValue) - } - public func Notification_GameScoreSelfExtended(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[40 * 6 + Int(form.rawValue)]!, stringValue) - } - public func PUSH_CHAT_MESSAGE_PHOTOS(_ selector: Int32, _ _2: String, _ _1: String, _ _3: Int32) -> String { - let form = getPluralizationForm(self.lc, selector) - return String(format: self._ps[41 * 6 + Int(form.rawValue)]!, _2, _1, _3) - } - public func VoiceOver_Chat_ContactEmailCount(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[42 * 6 + Int(form.rawValue)]!, stringValue) - } - public func Wallet_Updated_HoursAgo(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[43 * 6 + Int(form.rawValue)]!, stringValue) - } - public func SharedMedia_File(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[44 * 6 + Int(form.rawValue)]!, stringValue) - } - public func MessageTimer_ShortWeeks(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[45 * 6 + Int(form.rawValue)]!, stringValue) - } - public func PUSH_MESSAGE_ROUNDS(_ selector: Int32, _ _1: String, _ _2: Int32) -> String { - let form = getPluralizationForm(self.lc, selector) - return String(format: self._ps[46 * 6 + Int(form.rawValue)]!, _1, _2) - } - public func Passport_Scans(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[47 * 6 + Int(form.rawValue)]!, stringValue) - } - public func Invitation_Members(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[48 * 6 + Int(form.rawValue)]!, stringValue) - } - public func Wallpaper_DeleteConfirmation(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[49 * 6 + Int(form.rawValue)]!, stringValue) - } - public func PeopleNearby_ShowMorePeople(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[50 * 6 + Int(form.rawValue)]!, stringValue) - } - public func Map_ETAHours(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[51 * 6 + Int(form.rawValue)]!, stringValue) - } - public func Map_ETAMinutes(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[52 * 6 + Int(form.rawValue)]!, stringValue) - } - public func VoiceOver_Chat_PollVotes(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[53 * 6 + Int(form.rawValue)]!, stringValue) - } - public func PUSH_CHANNEL_MESSAGES(_ selector: Int32, _ _1: String, _ _2: Int32) -> String { - let form = getPluralizationForm(self.lc, selector) - return String(format: self._ps[54 * 6 + Int(form.rawValue)]!, _1, _2) - } - public func ForwardedMessages(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[55 * 6 + Int(form.rawValue)]!, stringValue) - } - public func Conversation_StatusSubscribers(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[56 * 6 + Int(form.rawValue)]!, stringValue) - } - public func MessageTimer_ShortSeconds(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[57 * 6 + Int(form.rawValue)]!, stringValue) - } - public func PUSH_CHAT_MESSAGE_ROUNDS(_ selector: Int32, _ _2: String, _ _1: String, _ _3: Int32) -> String { - let form = getPluralizationForm(self.lc, selector) - return String(format: self._ps[58 * 6 + Int(form.rawValue)]!, _2, _1, _3) - } - public func VoiceOver_Chat_PollOptionCount(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[59 * 6 + Int(form.rawValue)]!, stringValue) - } - public func Theme_UsersCount(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[60 * 6 + Int(form.rawValue)]!, stringValue) - } - public func Watch_LastSeen_HoursAgo(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[61 * 6 + Int(form.rawValue)]!, stringValue) - } - public func ForwardedAuthorsOthers(_ selector: Int32, _ _0: String, _ _1: String) -> String { - let form = getPluralizationForm(self.lc, selector) - return String(format: self._ps[62 * 6 + Int(form.rawValue)]!, _0, _1) - } - public func UserCount(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[63 * 6 + Int(form.rawValue)]!, stringValue) - } - public func LiveLocationUpdated_MinutesAgo(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[64 * 6 + Int(form.rawValue)]!, stringValue) - } - public func LiveLocation_MenuChatsCount(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[65 * 6 + Int(form.rawValue)]!, stringValue) - } - public func MessageTimer_Hours(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[66 * 6 + Int(form.rawValue)]!, stringValue) - } - public func CreatePoll_AddMoreOptions(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[67 * 6 + Int(form.rawValue)]!, stringValue) - } - public func Media_SharePhoto(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[68 * 6 + Int(form.rawValue)]!, stringValue) - } - public func Contacts_ImportersCount(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[69 * 6 + Int(form.rawValue)]!, stringValue) - } - public func AttachmentMenu_SendVideo(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[70 * 6 + Int(form.rawValue)]!, stringValue) - } - public func GroupInfo_ParticipantCount(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[71 * 6 + Int(form.rawValue)]!, stringValue) - } - public func MessageTimer_Seconds(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[72 * 6 + Int(form.rawValue)]!, stringValue) - } - public func AttachmentMenu_SendPhoto(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[73 * 6 + Int(form.rawValue)]!, stringValue) - } - public func Call_Minutes(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[74 * 6 + Int(form.rawValue)]!, stringValue) - } - public func MuteFor_Days(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[75 * 6 + Int(form.rawValue)]!, stringValue) - } - public func MuteExpires_Minutes(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[76 * 6 + Int(form.rawValue)]!, stringValue) - } - public func ForwardedContacts(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[77 * 6 + Int(form.rawValue)]!, stringValue) - } - public func PUSH_CHAT_MESSAGE_VIDEOS(_ selector: Int32, _ _2: String, _ _1: String, _ _3: Int32) -> String { - let form = getPluralizationForm(self.lc, selector) - return String(format: self._ps[78 * 6 + Int(form.rawValue)]!, _2, _1, _3) - } - public func SharedMedia_Video(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[79 * 6 + Int(form.rawValue)]!, stringValue) - } - public func OldChannels_Leave(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[80 * 6 + Int(form.rawValue)]!, stringValue) - } - public func ForwardedPhotos(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[81 * 6 + Int(form.rawValue)]!, stringValue) - } - public func Notifications_ExceptionMuteExpires_Hours(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[82 * 6 + Int(form.rawValue)]!, stringValue) - } - public func ForwardedVideoMessages(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[83 * 6 + Int(form.rawValue)]!, stringValue) - } - public func StickerPack_AddMaskCount(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[84 * 6 + Int(form.rawValue)]!, stringValue) - } - public func MessageTimer_Minutes(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[85 * 6 + Int(form.rawValue)]!, stringValue) - } - public func ServiceMessage_GameScoreSelfExtended(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[86 * 6 + Int(form.rawValue)]!, stringValue) - } - public func ChatList_DeletedChats(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[87 * 6 + Int(form.rawValue)]!, stringValue) - } - public func StickerPack_RemoveStickerCount(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[88 * 6 + Int(form.rawValue)]!, stringValue) - } - public func PollResults_ShowMore(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[89 * 6 + Int(form.rawValue)]!, stringValue) - } - public func LastSeen_MinutesAgo(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[90 * 6 + Int(form.rawValue)]!, stringValue) - } - public func ForwardedLocations(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[91 * 6 + Int(form.rawValue)]!, stringValue) - } - public func PUSH_CHANNEL_MESSAGE_ROUNDS(_ selector: Int32, _ _1: String, _ _2: Int32) -> String { - let form = getPluralizationForm(self.lc, selector) - return String(format: self._ps[92 * 6 + Int(form.rawValue)]!, _1, _2) - } - public func ForwardedFiles(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[93 * 6 + Int(form.rawValue)]!, stringValue) - } - public func Conversation_LiveLocationMembersCount(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[94 * 6 + Int(form.rawValue)]!, stringValue) - } - public func VoiceOver_Chat_ContactPhoneNumberCount(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[95 * 6 + Int(form.rawValue)]!, stringValue) - } - public func PUSH_CHAT_MESSAGE_FWDS(_ selector: Int32, _ _2: String, _ _1: String, _ _3: Int32) -> String { - let form = getPluralizationForm(self.lc, selector) - return String(format: self._ps[96 * 6 + Int(form.rawValue)]!, _2, _1, _3) - } - public func OldChannels_InactiveYear(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[97 * 6 + Int(form.rawValue)]!, stringValue) - } - public func MessageTimer_ShortDays(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[98 * 6 + Int(form.rawValue)]!, stringValue) - } - public func PUSH_CHANNEL_MESSAGE_PHOTOS(_ selector: Int32, _ _1: String, _ _2: Int32) -> String { - let form = getPluralizationForm(self.lc, selector) - return String(format: self._ps[99 * 6 + Int(form.rawValue)]!, _1, _2) - } - public func OldChannels_InactiveWeek(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[100 * 6 + Int(form.rawValue)]!, stringValue) - } - public func MessageTimer_ShortMinutes(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[101 * 6 + Int(form.rawValue)]!, stringValue) - } - public func MessageTimer_Months(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[102 * 6 + Int(form.rawValue)]!, stringValue) - } - public func PUSH_CHANNEL_MESSAGE_VIDEOS(_ selector: Int32, _ _1: String, _ _2: Int32) -> String { - let form = getPluralizationForm(self.lc, selector) - return String(format: self._ps[103 * 6 + Int(form.rawValue)]!, _1, _2) - } - public func SharedMedia_Photo(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[104 * 6 + Int(form.rawValue)]!, stringValue) - } public func SharedMedia_Link(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[105 * 6 + Int(form.rawValue)]!, stringValue) - } - public func AttachmentMenu_SendItem(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[106 * 6 + Int(form.rawValue)]!, stringValue) - } - public func Media_ShareItem(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[107 * 6 + Int(form.rawValue)]!, stringValue) - } - public func Notifications_ExceptionMuteExpires_Days(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[108 * 6 + Int(form.rawValue)]!, stringValue) - } - public func PUSH_CHANNEL_MESSAGE_FWDS(_ selector: Int32, _ _1: String, _ _2: Int32) -> String { - let form = getPluralizationForm(self.lc, selector) - return String(format: self._ps[109 * 6 + Int(form.rawValue)]!, _1, _2) + return String(format: self._ps[7 * 6 + Int(form.rawValue)]!, stringValue) } public func Call_Seconds(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[110 * 6 + Int(form.rawValue)]!, stringValue) + return String(format: self._ps[8 * 6 + Int(form.rawValue)]!, stringValue) } - public func MessagePoll_QuizCount(_ value: Int32) -> String { + public func PUSH_MESSAGE_FWDS(_ selector: Int32, _ _1: String, _ _2: Int32) -> String { + let form = getPluralizationForm(self.lc, selector) + return String(format: self._ps[9 * 6 + Int(form.rawValue)]!, _1, _2) + } + public func Media_ShareItem(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[111 * 6 + Int(form.rawValue)]!, stringValue) + return String(format: self._ps[10 * 6 + Int(form.rawValue)]!, stringValue) } - public func Watch_UserInfo_Mute(_ value: Int32) -> String { + public func PUSH_MESSAGES(_ selector: Int32, _ _1: String, _ _2: Int32) -> String { + let form = getPluralizationForm(self.lc, selector) + return String(format: self._ps[11 * 6 + Int(form.rawValue)]!, _1, _2) + } + public func ForwardedPolls(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[112 * 6 + Int(form.rawValue)]!, stringValue) + return String(format: self._ps[12 * 6 + Int(form.rawValue)]!, stringValue) } - public func Forward_ConfirmMultipleFiles(_ value: Int32) -> String { + public func GroupInfo_ParticipantCount(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[113 * 6 + Int(form.rawValue)]!, stringValue) + return String(format: self._ps[13 * 6 + Int(form.rawValue)]!, stringValue) } public func MessageTimer_Years(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[114 * 6 + Int(form.rawValue)]!, stringValue) + return String(format: self._ps[14 * 6 + Int(form.rawValue)]!, stringValue) } - public func PUSH_MESSAGE_FWDS(_ selector: Int32, _ _1: String, _ _2: Int32) -> String { + public func Notifications_ExceptionMuteExpires_Days(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[15 * 6 + Int(form.rawValue)]!, stringValue) + } + public func Conversation_StatusMembers(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[16 * 6 + Int(form.rawValue)]!, stringValue) + } + public func PUSH_CHAT_MESSAGE_VIDEOS(_ selector: Int32, _ _2: String, _ _1: String, _ _3: Int32) -> String { let form = getPluralizationForm(self.lc, selector) - return String(format: self._ps[115 * 6 + Int(form.rawValue)]!, _1, _2) + return String(format: self._ps[17 * 6 + Int(form.rawValue)]!, _2, _1, _3) + } + public func SharedMedia_Generic(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[18 * 6 + Int(form.rawValue)]!, stringValue) + } + public func MessageTimer_ShortHours(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[19 * 6 + Int(form.rawValue)]!, stringValue) + } + public func Conversation_StatusSubscribers(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[20 * 6 + Int(form.rawValue)]!, stringValue) + } + public func PUSH_CHANNEL_MESSAGE_ROUNDS(_ selector: Int32, _ _1: String, _ _2: Int32) -> String { + let form = getPluralizationForm(self.lc, selector) + return String(format: self._ps[21 * 6 + Int(form.rawValue)]!, _1, _2) + } + public func MessagePoll_VotedCount(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[22 * 6 + Int(form.rawValue)]!, stringValue) + } + public func CreatePoll_AddMoreOptions(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[23 * 6 + Int(form.rawValue)]!, stringValue) + } + public func SharedMedia_File(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[24 * 6 + Int(form.rawValue)]!, stringValue) + } + public func PUSH_MESSAGE_ROUNDS(_ selector: Int32, _ _1: String, _ _2: Int32) -> String { + let form = getPluralizationForm(self.lc, selector) + return String(format: self._ps[25 * 6 + Int(form.rawValue)]!, _1, _2) + } + public func Call_ShortMinutes(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[26 * 6 + Int(form.rawValue)]!, stringValue) + } + public func ChatList_DeletedChats(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[27 * 6 + Int(form.rawValue)]!, stringValue) + } + public func SharedMedia_Photo(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[28 * 6 + Int(form.rawValue)]!, stringValue) + } + public func Conversation_LiveLocationMembersCount(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[29 * 6 + Int(form.rawValue)]!, stringValue) + } + public func Conversation_SelectedMessages(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[30 * 6 + Int(form.rawValue)]!, stringValue) + } + public func Media_ShareVideo(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[31 * 6 + Int(form.rawValue)]!, stringValue) + } + public func MuteExpires_Hours(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[32 * 6 + Int(form.rawValue)]!, stringValue) + } + public func StickerPack_AddStickerCount(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[33 * 6 + Int(form.rawValue)]!, stringValue) + } + public func Call_Minutes(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[34 * 6 + Int(form.rawValue)]!, stringValue) } public func Notification_GameScoreSelfSimple(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[116 * 6 + Int(form.rawValue)]!, stringValue) + return String(format: self._ps[35 * 6 + Int(form.rawValue)]!, stringValue) + } + public func PUSH_CHAT_MESSAGE_FWDS(_ selector: Int32, _ _2: String, _ _1: String, _ _3: Int32) -> String { + let form = getPluralizationForm(self.lc, selector) + return String(format: self._ps[36 * 6 + Int(form.rawValue)]!, _2, _1, _3) } public func MessageTimer_Days(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[117 * 6 + Int(form.rawValue)]!, stringValue) + return String(format: self._ps[37 * 6 + Int(form.rawValue)]!, stringValue) + } + public func GroupInfo_ShowMoreMembers(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[38 * 6 + Int(form.rawValue)]!, stringValue) + } + public func LiveLocationUpdated_MinutesAgo(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[39 * 6 + Int(form.rawValue)]!, stringValue) + } + public func MessageTimer_ShortSeconds(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[40 * 6 + Int(form.rawValue)]!, stringValue) + } + public func ForwardedVideoMessages(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[41 * 6 + Int(form.rawValue)]!, stringValue) + } + public func Map_ETAMinutes(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[42 * 6 + Int(form.rawValue)]!, stringValue) + } + public func OldChannels_InactiveYear(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[43 * 6 + Int(form.rawValue)]!, stringValue) + } + public func ForwardedFiles(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[44 * 6 + Int(form.rawValue)]!, stringValue) + } + public func Notification_GameScoreExtended(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[45 * 6 + Int(form.rawValue)]!, stringValue) + } + public func Forward_ConfirmMultipleFiles(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[46 * 6 + Int(form.rawValue)]!, stringValue) + } + public func PrivacyLastSeenSettings_AddUsers(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[47 * 6 + Int(form.rawValue)]!, stringValue) + } + public func PUSH_CHAT_MESSAGE_ROUNDS(_ selector: Int32, _ _2: String, _ _1: String, _ _3: Int32) -> String { + let form = getPluralizationForm(self.lc, selector) + return String(format: self._ps[48 * 6 + Int(form.rawValue)]!, _2, _1, _3) + } + public func MessageTimer_ShortWeeks(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[49 * 6 + Int(form.rawValue)]!, stringValue) } public func PUSH_MESSAGE_PHOTOS(_ selector: Int32, _ _1: String, _ _2: Int32) -> String { let form = getPluralizationForm(self.lc, selector) - return String(format: self._ps[118 * 6 + Int(form.rawValue)]!, _1, _2) + return String(format: self._ps[50 * 6 + Int(form.rawValue)]!, _1, _2) } - public func ForwardedAudios(_ value: Int32) -> String { + public func UserCount(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[119 * 6 + Int(form.rawValue)]!, stringValue) + return String(format: self._ps[51 * 6 + Int(form.rawValue)]!, stringValue) } - public func AttachmentMenu_SendGif(_ value: Int32) -> String { + public func ChatList_DeleteConfirmation(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[120 * 6 + Int(form.rawValue)]!, stringValue) + return String(format: self._ps[52 * 6 + Int(form.rawValue)]!, stringValue) } - public func MuteFor_Hours(_ value: Int32) -> String { + public func SharedMedia_Video(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[121 * 6 + Int(form.rawValue)]!, stringValue) - } - public func ChatList_SelectedChats(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[122 * 6 + Int(form.rawValue)]!, stringValue) + return String(format: self._ps[53 * 6 + Int(form.rawValue)]!, stringValue) } public func Conversation_StatusOnline(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[123 * 6 + Int(form.rawValue)]!, stringValue) + return String(format: self._ps[54 * 6 + Int(form.rawValue)]!, stringValue) + } + public func AttachmentMenu_SendPhoto(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[55 * 6 + Int(form.rawValue)]!, stringValue) + } + public func Passport_Scans(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[56 * 6 + Int(form.rawValue)]!, stringValue) + } + public func StickerPack_RemoveMaskCount(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[57 * 6 + Int(form.rawValue)]!, stringValue) + } + public func MessageTimer_Seconds(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[58 * 6 + Int(form.rawValue)]!, stringValue) + } + public func MessageTimer_Hours(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[59 * 6 + Int(form.rawValue)]!, stringValue) + } + public func VoiceOver_Chat_ContactPhoneNumberCount(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[60 * 6 + Int(form.rawValue)]!, stringValue) + } + public func ServiceMessage_GameScoreSelfExtended(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[61 * 6 + Int(form.rawValue)]!, stringValue) + } + public func StickerPack_AddMaskCount(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[62 * 6 + Int(form.rawValue)]!, stringValue) + } + public func AttachmentMenu_SendItem(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[63 * 6 + Int(form.rawValue)]!, stringValue) + } + public func MuteExpires_Days(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[64 * 6 + Int(form.rawValue)]!, stringValue) + } + public func Watch_UserInfo_Mute(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[65 * 6 + Int(form.rawValue)]!, stringValue) + } + public func LiveLocation_MenuChatsCount(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[66 * 6 + Int(form.rawValue)]!, stringValue) + } + public func ForwardedGifs(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[67 * 6 + Int(form.rawValue)]!, stringValue) + } + public func ForwardedContacts(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[68 * 6 + Int(form.rawValue)]!, stringValue) + } + public func ForwardedPhotos(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[69 * 6 + Int(form.rawValue)]!, stringValue) + } + public func ForwardedMessages(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[70 * 6 + Int(form.rawValue)]!, stringValue) + } + public func AttachmentMenu_SendVideo(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[71 * 6 + Int(form.rawValue)]!, stringValue) + } + public func Watch_LastSeen_MinutesAgo(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[72 * 6 + Int(form.rawValue)]!, stringValue) + } + public func PUSH_CHAT_MESSAGES(_ selector: Int32, _ _2: String, _ _1: String, _ _3: Int32) -> String { + let form = getPluralizationForm(self.lc, selector) + return String(format: self._ps[73 * 6 + Int(form.rawValue)]!, _2, _1, _3) + } + public func DialogList_LiveLocationChatsCount(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[74 * 6 + Int(form.rawValue)]!, stringValue) + } + public func MessageTimer_Months(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[75 * 6 + Int(form.rawValue)]!, stringValue) + } + public func LastSeen_MinutesAgo(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[76 * 6 + Int(form.rawValue)]!, stringValue) + } + public func ForwardedLocations(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[77 * 6 + Int(form.rawValue)]!, stringValue) + } + public func PUSH_CHANNEL_MESSAGE_FWDS(_ selector: Int32, _ _1: String, _ _2: Int32) -> String { + let form = getPluralizationForm(self.lc, selector) + return String(format: self._ps[78 * 6 + Int(form.rawValue)]!, _1, _2) + } + public func Invitation_Members(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[79 * 6 + Int(form.rawValue)]!, stringValue) + } + public func PeopleNearby_ShowMorePeople(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[80 * 6 + Int(form.rawValue)]!, stringValue) + } + public func OldChannels_InactiveMonth(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[81 * 6 + Int(form.rawValue)]!, stringValue) + } + public func MessagePoll_QuizCount(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[82 * 6 + Int(form.rawValue)]!, stringValue) + } + public func MessageTimer_Minutes(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[83 * 6 + Int(form.rawValue)]!, stringValue) + } + public func MuteExpires_Minutes(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[84 * 6 + Int(form.rawValue)]!, stringValue) + } + public func MuteFor_Hours(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[85 * 6 + Int(form.rawValue)]!, stringValue) + } + public func VoiceOver_Chat_PollOptionCount(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[86 * 6 + Int(form.rawValue)]!, stringValue) + } + public func Contacts_ImportersCount(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[87 * 6 + Int(form.rawValue)]!, stringValue) + } + public func Chat_DeleteMessagesConfirmation(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[88 * 6 + Int(form.rawValue)]!, stringValue) + } + public func Contacts_InviteContacts(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[89 * 6 + Int(form.rawValue)]!, stringValue) + } + public func VoiceOver_Chat_ContactEmailCount(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[90 * 6 + Int(form.rawValue)]!, stringValue) + } + public func InviteText_ContactsCountText(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[91 * 6 + Int(form.rawValue)]!, stringValue) + } + public func ChatList_SelectedChats(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[92 * 6 + Int(form.rawValue)]!, stringValue) + } + public func MuteFor_Days(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[93 * 6 + Int(form.rawValue)]!, stringValue) + } + public func PUSH_CHAT_MESSAGE_PHOTOS(_ selector: Int32, _ _2: String, _ _1: String, _ _3: Int32) -> String { + let form = getPluralizationForm(self.lc, selector) + return String(format: self._ps[94 * 6 + Int(form.rawValue)]!, _2, _1, _3) + } + public func QuickSend_Photos(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[95 * 6 + Int(form.rawValue)]!, stringValue) + } + public func OldChannels_InactiveWeek(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[96 * 6 + Int(form.rawValue)]!, stringValue) + } + public func MessageTimer_ShortDays(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[97 * 6 + Int(form.rawValue)]!, stringValue) + } + public func PollResults_ShowMore(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[98 * 6 + Int(form.rawValue)]!, stringValue) + } + public func ForwardedAudios(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[99 * 6 + Int(form.rawValue)]!, stringValue) + } + public func StickerPack_RemoveStickerCount(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[100 * 6 + Int(form.rawValue)]!, stringValue) + } + public func AttachmentMenu_SendGif(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[101 * 6 + Int(form.rawValue)]!, stringValue) + } + public func PUSH_CHANNEL_MESSAGE_PHOTOS(_ selector: Int32, _ _1: String, _ _2: Int32) -> String { + let form = getPluralizationForm(self.lc, selector) + return String(format: self._ps[102 * 6 + Int(form.rawValue)]!, _1, _2) + } + public func VoiceOver_Chat_PollVotes(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[103 * 6 + Int(form.rawValue)]!, stringValue) + } + public func Theme_UsersCount(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[104 * 6 + Int(form.rawValue)]!, stringValue) } public func ForwardedStickers(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[124 * 6 + Int(form.rawValue)]!, stringValue) + return String(format: self._ps[105 * 6 + Int(form.rawValue)]!, stringValue) + } + public func Notifications_Exceptions(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[106 * 6 + Int(form.rawValue)]!, stringValue) + } + public func ServiceMessage_GameScoreSimple(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[107 * 6 + Int(form.rawValue)]!, stringValue) + } + public func Media_SharePhoto(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[108 * 6 + Int(form.rawValue)]!, stringValue) } public func Notification_GameScoreSimple(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[109 * 6 + Int(form.rawValue)]!, stringValue) + } + public func Notifications_ExceptionMuteExpires_Minutes(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[110 * 6 + Int(form.rawValue)]!, stringValue) + } + public func OldChannels_Leave(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[111 * 6 + Int(form.rawValue)]!, stringValue) + } + public func PUSH_CHANNEL_MESSAGE_VIDEOS(_ selector: Int32, _ _1: String, _ _2: Int32) -> String { + let form = getPluralizationForm(self.lc, selector) + return String(format: self._ps[112 * 6 + Int(form.rawValue)]!, _1, _2) + } + public func SharedMedia_DeleteItemsConfirmation(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[113 * 6 + Int(form.rawValue)]!, stringValue) + } + public func Notification_GameScoreSelfExtended(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[114 * 6 + Int(form.rawValue)]!, stringValue) + } + public func Call_ShortSeconds(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[115 * 6 + Int(form.rawValue)]!, stringValue) + } + public func PUSH_CHANNEL_MESSAGES(_ selector: Int32, _ _1: String, _ _2: Int32) -> String { + let form = getPluralizationForm(self.lc, selector) + return String(format: self._ps[116 * 6 + Int(form.rawValue)]!, _1, _2) + } + public func ForwardedVideos(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[117 * 6 + Int(form.rawValue)]!, stringValue) + } + public func Watch_LastSeen_HoursAgo(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[118 * 6 + Int(form.rawValue)]!, stringValue) + } + public func PUSH_MESSAGE_VIDEOS(_ selector: Int32, _ _1: String, _ _2: Int32) -> String { + let form = getPluralizationForm(self.lc, selector) + return String(format: self._ps[119 * 6 + Int(form.rawValue)]!, _1, _2) + } + public func Notifications_ExceptionMuteExpires_Hours(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[120 * 6 + Int(form.rawValue)]!, stringValue) + } + public func ForwardedAuthorsOthers(_ selector: Int32, _ _0: String, _ _1: String) -> String { + let form = getPluralizationForm(self.lc, selector) + return String(format: self._ps[121 * 6 + Int(form.rawValue)]!, _0, _1) + } + public func MessageTimer_Weeks(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[122 * 6 + Int(form.rawValue)]!, stringValue) + } + public func Wallet_Updated_MinutesAgo(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[123 * 6 + Int(form.rawValue)]!, stringValue) + } + public func LastSeen_HoursAgo(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[124 * 6 + Int(form.rawValue)]!, stringValue) + } + public func OldChannels_GroupFormat(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) return String(format: self._ps[125 * 6 + Int(form.rawValue)]!, stringValue) } - public func Media_ShareVideo(_ value: Int32) -> String { + public func ServiceMessage_GameScoreExtended(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) return String(format: self._ps[126 * 6 + Int(form.rawValue)]!, stringValue) diff --git a/submodules/TelegramPresentationData/Sources/PresentationTheme.swift b/submodules/TelegramPresentationData/Sources/PresentationTheme.swift index b5696e11e6..003c2d1246 100644 --- a/submodules/TelegramPresentationData/Sources/PresentationTheme.swift +++ b/submodules/TelegramPresentationData/Sources/PresentationTheme.swift @@ -261,8 +261,11 @@ public final class PresentationThemeContextMenu { public let primaryColor: UIColor public let secondaryColor: UIColor public let destructiveColor: UIColor + public let badgeFillColor: UIColor + public let badgeForegroundColor: UIColor + public let extractedContentTintColor: UIColor - init(dimColor: UIColor, backgroundColor: UIColor, itemSeparatorColor: UIColor, sectionSeparatorColor: UIColor, itemBackgroundColor: UIColor, itemHighlightedBackgroundColor: UIColor, primaryColor: UIColor, secondaryColor: UIColor, destructiveColor: UIColor) { + init(dimColor: UIColor, backgroundColor: UIColor, itemSeparatorColor: UIColor, sectionSeparatorColor: UIColor, itemBackgroundColor: UIColor, itemHighlightedBackgroundColor: UIColor, primaryColor: UIColor, secondaryColor: UIColor, destructiveColor: UIColor, badgeFillColor: UIColor, badgeForegroundColor: UIColor, extractedContentTintColor: UIColor) { self.dimColor = dimColor self.backgroundColor = backgroundColor self.itemSeparatorColor = itemSeparatorColor @@ -272,10 +275,13 @@ public final class PresentationThemeContextMenu { self.primaryColor = primaryColor self.secondaryColor = secondaryColor self.destructiveColor = destructiveColor + self.badgeFillColor = badgeFillColor + self.badgeForegroundColor = badgeForegroundColor + self.extractedContentTintColor = extractedContentTintColor } public func withUpdated(dimColor: UIColor? = nil, backgroundColor: UIColor? = nil, itemSeparatorColor: UIColor? = nil, sectionSeparatorColor: UIColor? = nil, itemBackgroundColor: UIColor? = nil, itemHighlightedBackgroundColor: UIColor? = nil, primaryColor: UIColor? = nil, secondaryColor: UIColor? = nil, destructiveColor: UIColor? = nil) -> PresentationThemeContextMenu { - return PresentationThemeContextMenu(dimColor: dimColor ?? self.dimColor, backgroundColor: backgroundColor ?? self.backgroundColor, itemSeparatorColor: itemSeparatorColor ?? self.itemSeparatorColor, sectionSeparatorColor: sectionSeparatorColor ?? self.sectionSeparatorColor, itemBackgroundColor: itemBackgroundColor ?? self.itemBackgroundColor, itemHighlightedBackgroundColor: itemHighlightedBackgroundColor ?? self.itemHighlightedBackgroundColor, primaryColor: primaryColor ?? self.primaryColor, secondaryColor: secondaryColor ?? self.secondaryColor, destructiveColor: destructiveColor ?? self.destructiveColor) + return PresentationThemeContextMenu(dimColor: dimColor ?? self.dimColor, backgroundColor: backgroundColor ?? self.backgroundColor, itemSeparatorColor: itemSeparatorColor ?? self.itemSeparatorColor, sectionSeparatorColor: sectionSeparatorColor ?? self.sectionSeparatorColor, itemBackgroundColor: itemBackgroundColor ?? self.itemBackgroundColor, itemHighlightedBackgroundColor: itemHighlightedBackgroundColor ?? self.itemHighlightedBackgroundColor, primaryColor: primaryColor ?? self.primaryColor, secondaryColor: secondaryColor ?? self.secondaryColor, destructiveColor: destructiveColor ?? self.destructiveColor, badgeFillColor: self.badgeFillColor, badgeForegroundColor: self.badgeForegroundColor, extractedContentTintColor: self.extractedContentTintColor) } } diff --git a/submodules/TelegramPresentationData/Sources/PresentationThemeCodable.swift b/submodules/TelegramPresentationData/Sources/PresentationThemeCodable.swift index 18b7119106..1eef46a36d 100644 --- a/submodules/TelegramPresentationData/Sources/PresentationThemeCodable.swift +++ b/submodules/TelegramPresentationData/Sources/PresentationThemeCodable.swift @@ -1629,19 +1629,28 @@ extension PresentationThemeContextMenu: Codable { case primary case secondary case destructive + case badgeFill + case badgeForeground + case extractedTint } public convenience init(from decoder: Decoder) throws { let values = try decoder.container(keyedBy: CodingKeys.self) - self.init(dimColor: try decodeColor(values, .dim), - backgroundColor: try decodeColor(values, .background), - itemSeparatorColor: try decodeColor(values, .itemSeparator), - sectionSeparatorColor: try decodeColor(values, .sectionSeparator), - itemBackgroundColor: try decodeColor(values, .itemBg), - itemHighlightedBackgroundColor: try decodeColor(values, .itemHighlightedBg), - primaryColor: try decodeColor(values, .primary), - secondaryColor: try decodeColor(values, .secondary), - destructiveColor: try decodeColor(values, .destructive) + let destructiveColor = try decodeColor(values, .destructive) + let backgroundColor = try decodeColor(values, .background) + self.init( + dimColor: try decodeColor(values, .dim), + backgroundColor: backgroundColor, + itemSeparatorColor: try decodeColor(values, .itemSeparator), + sectionSeparatorColor: try decodeColor(values, .sectionSeparator), + itemBackgroundColor: try decodeColor(values, .itemBg), + itemHighlightedBackgroundColor: try decodeColor(values, .itemHighlightedBg), + primaryColor: try decodeColor(values, .primary), + secondaryColor: try decodeColor(values, .secondary), + destructiveColor: destructiveColor, + badgeFillColor: (try? decodeColor(values, .badgeFill)) ?? destructiveColor, + badgeForegroundColor: (try? decodeColor(values, .badgeForeground)) ?? backgroundColor, + extractedContentTintColor: (try? decodeColor(values, .extractedTint)) ?? backgroundColor ) } diff --git a/submodules/TelegramUI/BUCK b/submodules/TelegramUI/BUCK index 0f5a9a43b8..b4036d3a53 100644 --- a/submodules/TelegramUI/BUCK +++ b/submodules/TelegramUI/BUCK @@ -202,6 +202,7 @@ framework( "//submodules/SemanticStatusNode:SemanticStatusNode", "//submodules/AccountUtils:AccountUtils", "//submodules/Svg:Svg", + "//submodules/StatisticsUI:StatisticsUI", ], frameworks = [ "$SDKROOT/System/Library/Frameworks/Foundation.framework", diff --git a/submodules/TelegramUI/Images.xcassets/Chart/arrow_left.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chart/arrow_left.imageset/Contents.json index 78b05b5e2a..147027fa6d 100644 --- a/submodules/TelegramUI/Images.xcassets/Chart/arrow_left.imageset/Contents.json +++ b/submodules/TelegramUI/Images.xcassets/Chart/arrow_left.imageset/Contents.json @@ -2,7 +2,7 @@ "images" : [ { "idiom" : "universal", - "filename" : "arrow_left.pdf" + "filename" : "arrow_right.pdf" } ], "info" : { diff --git a/submodules/TelegramUI/Images.xcassets/Chart/arrow_right.imageset/arrow_right.pdf b/submodules/TelegramUI/Images.xcassets/Chart/arrow_left.imageset/arrow_right.pdf similarity index 100% rename from submodules/TelegramUI/Images.xcassets/Chart/arrow_right.imageset/arrow_right.pdf rename to submodules/TelegramUI/Images.xcassets/Chart/arrow_left.imageset/arrow_right.pdf diff --git a/submodules/TelegramUI/Images.xcassets/Chart/arrow_right.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chart/arrow_right.imageset/Contents.json index 147027fa6d..78b05b5e2a 100644 --- a/submodules/TelegramUI/Images.xcassets/Chart/arrow_right.imageset/Contents.json +++ b/submodules/TelegramUI/Images.xcassets/Chart/arrow_right.imageset/Contents.json @@ -2,7 +2,7 @@ "images" : [ { "idiom" : "universal", - "filename" : "arrow_right.pdf" + "filename" : "arrow_left.pdf" } ], "info" : { diff --git a/submodules/TelegramUI/Images.xcassets/Chart/arrow_left.imageset/arrow_left.pdf b/submodules/TelegramUI/Images.xcassets/Chart/arrow_right.imageset/arrow_left.pdf similarity index 100% rename from submodules/TelegramUI/Images.xcassets/Chart/arrow_left.imageset/arrow_left.pdf rename to submodules/TelegramUI/Images.xcassets/Chart/arrow_right.imageset/arrow_left.pdf diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Bots.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Bots.imageset/Contents.json new file mode 100644 index 0000000000..27ac76fbf8 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Bots.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_bots.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Bots.imageset/ic_bots.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Bots.imageset/ic_bots.pdf new file mode 100644 index 0000000000..ade2669f78 Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Bots.imageset/ic_bots.pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Channels.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Channels.imageset/Contents.json new file mode 100644 index 0000000000..dfdb9b6f96 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Channels.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_channels.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Channels.imageset/ic_channels.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Channels.imageset/ic_channels.pdf new file mode 100644 index 0000000000..3cddff04fd Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Channels.imageset/ic_channels.pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Groups.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Groups.imageset/Contents.json new file mode 100644 index 0000000000..d5564b8a9d --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Groups.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_group.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Groups.imageset/ic_group.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Groups.imageset/ic_group.pdf new file mode 100644 index 0000000000..1decbe2d3f Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Groups.imageset/ic_group.pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/List.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/List.imageset/Contents.json new file mode 100644 index 0000000000..f9fa203bb7 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/List.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_list.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/List.imageset/ic_list.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/List.imageset/ic_list.pdf new file mode 100644 index 0000000000..2f376dc273 Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/List.imageset/ic_list.pdf differ diff --git a/submodules/TelegramUI/Resources/Animations/ChatListEmpty.tgs b/submodules/TelegramUI/Resources/Animations/ChatListEmpty.tgs new file mode 100644 index 0000000000..e4b8e01649 Binary files /dev/null and b/submodules/TelegramUI/Resources/Animations/ChatListEmpty.tgs differ diff --git a/submodules/TelegramUI/Resources/Animations/ChatListFilterEmpty.tgs b/submodules/TelegramUI/Resources/Animations/ChatListFilterEmpty.tgs new file mode 100644 index 0000000000..2dd3799596 Binary files /dev/null and b/submodules/TelegramUI/Resources/Animations/ChatListFilterEmpty.tgs differ diff --git a/submodules/TelegramUI/Resources/PresentationStrings.mapping b/submodules/TelegramUI/Resources/PresentationStrings.mapping index 2f6ecd7fdb..989b8c032d 100644 Binary files a/submodules/TelegramUI/Resources/PresentationStrings.mapping and b/submodules/TelegramUI/Resources/PresentationStrings.mapping differ diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 70cbd9adec..9ccecd3177 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -534,9 +534,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G strongSelf.controllerInteraction?.addContact(phoneNumber) } }, storeMediaPlaybackState: { [weak self] messageId, timestamp in - guard let strongSelf = self else { - return - } var storedState: MediaPlaybackStoredState? if let timestamp = timestamp { storedState = MediaPlaybackStoredState(timestamp: timestamp, playbackRate: .x1) @@ -1365,32 +1362,61 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return } - let _ = (getBankCardInfo(account: strongSelf.context.account, cardNumber: number) - |> deliverOnMainQueue).start(next: { [weak self] info in - if let strongSelf = self, let info = info { - let actionSheet = ActionSheetController(presentationData: strongSelf.presentationData) - var items: [ActionSheetItem] = [] - items.append(ActionSheetTextItem(title: info.title)) - for url in info.urls { - items.append(ActionSheetButtonItem(title: url.title, color: .accent, action: { [weak actionSheet] in - actionSheet?.dismissAnimated() - if let strongSelf = self { - strongSelf.controllerInteraction?.openUrl(url.url, false, false, message) - } - })) - } - items.append(ActionSheetButtonItem(title: strongSelf.presentationData.strings.Conversation_LinkDialogCopy, color: .accent, action: { [weak actionSheet] in - actionSheet?.dismissAnimated() - UIPasteboard.general.string = number - })) - actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [ - ActionSheetButtonItem(title: strongSelf.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in - actionSheet?.dismissAnimated() - }) - ])]) - strongSelf.present(actionSheet, in: .window(.root)) - } - }) +// var signal = getBankCardInfo(account: strongSelf.context.account, cardNumber: number) +// +// var cancelImpl: (() -> Void)? +// let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } +// let progressSignal = Signal { subscriber in +// let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: { +// cancelImpl?() +// })) +// strongSelf.present(controller, in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) +// return ActionDisposable { [weak controller] in +// Queue.mainQueue().async() { +// controller?.dismiss() +// } +// } +// } +// |> runOn(Queue.mainQueue()) +// |> delay(0.15, queue: Queue.mainQueue()) +// let progressDisposable = progressSignal.start() +// +// signal = signal +// |> afterDisposed { +// Queue.mainQueue().async { +// progressDisposable.dispose() +// } +// } +// cancelImpl = { +// disposable.set(nil) +// } +// disposable.set((signal +// |> deliverOnMainQueue).start(next: { [weak self] info in +// if let strongSelf = self, let info = info { +// let actionSheet = ActionSheetController(presentationData: strongSelf.presentationData) +// var items: [ActionSheetItem] = [] +// items.append(ActionSheetTextItem(title: info.title)) +// for url in info.urls { +// items.append(ActionSheetButtonItem(title: url.title, color: .accent, action: { [weak actionSheet] in +// actionSheet?.dismissAnimated() +// if let strongSelf = self { +// strongSelf.controllerInteraction?.openUrl(url.url, false, false, message) +// } +// })) +// } +// items.append(ActionSheetButtonItem(title: strongSelf.presentationData.strings.Conversation_LinkDialogCopy, color: .accent, action: { [weak actionSheet] in +// actionSheet?.dismissAnimated() +// UIPasteboard.general.string = number +// })) +// actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [ +// ActionSheetButtonItem(title: strongSelf.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in +// actionSheet?.dismissAnimated() +// }) +// ])]) +// strongSelf.present(actionSheet, in: .window(.root)) +// } +// })) + strongSelf.chatDisplayNode.dismissInput() } } @@ -1618,7 +1644,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G guard let strongSelf = self, let resultPoll = resultPoll else { return } - guard let _ = strongSelf.chatDisplayNode.historyNode.messageInCurrentHistoryView(id) else { + guard let message = strongSelf.chatDisplayNode.historyNode.messageInCurrentHistoryView(id) else { return } @@ -3390,6 +3416,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } + let editingMessage = strongSelf.editingMessage let text = trimChatInputText(convertMarkdownToAttributes(editMessage.inputState.inputText)) let entities = generateTextEntities(text.string, enabledTypes: .all, currentEntities: generateChatInputTextEntities(text)) var entitiesAttribute: TextEntitiesMessageAttribute? @@ -6713,6 +6740,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G guard let strongSelf = self else { return } + let complete = results.completed var navigateIndex: MessageIndex? strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { current in if let data = current.search { @@ -6763,6 +6791,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G guard let strongSelf = self else { return } + let complete = results.completed strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { current in if let data = current.search, let previousResultsState = data.resultsState { let messageIndices = results.messages.map({ $0.index }).sorted() diff --git a/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift index 7ff5e59baa..567dab973e 100644 --- a/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift @@ -228,7 +228,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode self?.accessibilityElementDidBecomeFocused() } - self.contextSourceNode.willUpdateIsExtractedToContextPreview = { [weak self] isExtractedToContextPreview in + self.contextSourceNode.willUpdateIsExtractedToContextPreview = { [weak self] isExtractedToContextPreview, _ in guard let strongSelf = self, let item = strongSelf.item else { return } diff --git a/submodules/TelegramUI/Sources/ChatMessageContextControllerContentSource.swift b/submodules/TelegramUI/Sources/ChatMessageContextControllerContentSource.swift index ac481544af..2b3bf7c229 100644 --- a/submodules/TelegramUI/Sources/ChatMessageContextControllerContentSource.swift +++ b/submodules/TelegramUI/Sources/ChatMessageContextControllerContentSource.swift @@ -5,6 +5,8 @@ import ContextUI import Postbox final class ChatMessageContextExtractedContentSource: ContextExtractedContentSource { + let keepInPlace: Bool = false + private weak var chatNode: ChatControllerNode? private let message: Message diff --git a/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift b/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift index eaf72dfcfe..227af409ed 100644 --- a/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift +++ b/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift @@ -78,7 +78,7 @@ private enum ChatListSearchEntry: Comparable, Identifiable { public func item(context: AccountContext, interaction: ChatListNodeInteraction) -> ListViewItem { switch self { case let .message(message, peer, readState, presentationData): - return ChatListItem(presentationData: presentationData, context: context, peerGroupId: .root, index: ChatListIndex(pinningIndex: nil, messageIndex: message.index), content: .peer(message: message, peer: peer, combinedReadState: readState, notificationSettings: nil, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: true, displayAsMessage: true, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction) + return ChatListItem(presentationData: presentationData, context: context, peerGroupId: .root, isInFilter: false, index: ChatListIndex(pinningIndex: nil, messageIndex: message.index), content: .peer(message: message, peer: peer, combinedReadState: readState, notificationSettings: nil, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: true, displayAsMessage: true, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction) } } } @@ -204,6 +204,7 @@ class ChatSearchResultsControllerNode: ViewControllerTracingNode, UIScrollViewDe default: gesture?.cancel() } + }, present: { _ in }) interaction.searchTextHighightState = searchQuery self.interaction = interaction diff --git a/submodules/TelegramUI/Sources/ContactMultiselectionControllerNode.swift b/submodules/TelegramUI/Sources/ContactMultiselectionControllerNode.swift index b276f8211c..bf5aa6a548 100644 --- a/submodules/TelegramUI/Sources/ContactMultiselectionControllerNode.swift +++ b/submodules/TelegramUI/Sources/ContactMultiselectionControllerNode.swift @@ -56,8 +56,8 @@ final class ContactMultiselectionControllerNode: ASDisplayNode { let placeholder: String var includeChatList = false switch mode { - case let .peerSelection(_, searchGroups): - includeChatList = searchGroups + case let .peerSelection(_, searchGroups, searchChannels): + includeChatList = searchGroups || searchChannels if searchGroups { placeholder = self.presentationData.strings.Contacts_SearchUsersAndGroupsLabel } else { @@ -108,11 +108,13 @@ final class ContactMultiselectionControllerNode: ASDisplayNode { } var searchChatList = false var searchGroups = false + var searchChannels = false if case let .peerSelection(peerSelection) = mode { searchChatList = peerSelection.searchChatList searchGroups = peerSelection.searchGroups + searchChannels = peerSelection.searchChannels } - let searchResultsNode = ContactListNode(context: context, presentation: .single(.search(signal: searchText.get(), searchChatList: searchChatList, searchDeviceContacts: false, searchGroups: searchGroups)), filters: filters, selectionState: selectionState) + let searchResultsNode = ContactListNode(context: context, presentation: .single(.search(signal: searchText.get(), searchChatList: searchChatList, searchDeviceContacts: false, searchGroups: searchGroups, searchChannels: searchChannels)), filters: filters, selectionState: selectionState) searchResultsNode.openPeer = { peer in self?.tokenListNode.setText("") self?.openPeer?(peer) diff --git a/submodules/TelegramUI/Sources/FetchVideoMediaResource.swift b/submodules/TelegramUI/Sources/FetchVideoMediaResource.swift index 808a58eaf0..dec88cecf3 100644 --- a/submodules/TelegramUI/Sources/FetchVideoMediaResource.swift +++ b/submodules/TelegramUI/Sources/FetchVideoMediaResource.swift @@ -65,7 +65,9 @@ struct VideoConversionConfiguration { } static func with(appConfiguration: AppConfiguration) -> VideoConversionConfiguration { + #if DEBUG return VideoConversionConfiguration(remuxToFMp4: true) + #endif if let data = appConfiguration.data, let conversion = data["video_conversion"] as? [String: Any] { let remuxToFMp4 = conversion["remux_fmp4"] as? Bool ?? VideoConversionConfiguration.defaultValue.remuxToFMp4 diff --git a/submodules/TelegramUI/Sources/ListMessageDateHeader.swift b/submodules/TelegramUI/Sources/ListMessageDateHeader.swift index 44f4e1aa1b..58bc5edb35 100644 --- a/submodules/TelegramUI/Sources/ListMessageDateHeader.swift +++ b/submodules/TelegramUI/Sources/ListMessageDateHeader.swift @@ -1,6 +1,7 @@ import Foundation import UIKit import Display +import SyncCore import AsyncDisplayKit import TelegramPresentationData import TelegramUIPreferences @@ -15,7 +16,9 @@ private let timezoneOffset: Int32 = { }() func listMessageDateHeaderId(timestamp: Int32) -> Int64 { - var time: time_t = time_t(timestamp + timezoneOffset) + var unclippedValue: Int64 = min(Int64(Int32.max), Int64(timestamp) + Int64(timezoneOffset)) + + var time: time_t = time_t(Int32(clamping: unclippedValue)) var timeinfo: tm = tm() localtime_r(&time, &timeinfo) diff --git a/submodules/TelegramUI/Sources/NotificationContentContext.swift b/submodules/TelegramUI/Sources/NotificationContentContext.swift index 4950f319d8..415104c251 100644 --- a/submodules/TelegramUI/Sources/NotificationContentContext.swift +++ b/submodules/TelegramUI/Sources/NotificationContentContext.swift @@ -207,14 +207,14 @@ public final class NotificationViewControllerImpl { let mediaBoxPath = accountsPath + "/" + accountRecordIdPathName(AccountRecordId(rawValue: accountIdValue)) + "/postbox/media" if let data = try? Data(contentsOf: URL(fileURLWithPath: mediaBoxPath + "/\(largestRepresentation.resource.id.uniqueId)"), options: .mappedRead) { - self.imageNode.setSignal(chatMessagePhotoInternal(photoData: .single(Tuple(nil, data, true))) - |> map { $0.1 }) + self.imageNode.setSignal(chatMessagePhotoInternal(photoData: .single(Tuple(nil, data, .full, true))) + |> map { $0.2 }) return } if let data = try? Data(contentsOf: URL(fileURLWithPath: mediaBoxPath + "/\(thumbnailRepresentation.resource.id.uniqueId)"), options: .mappedRead) { - self.imageNode.setSignal(chatMessagePhotoInternal(photoData: .single(Tuple(data, nil, false))) - |> map { $0.1 }) + self.imageNode.setSignal(chatMessagePhotoInternal(photoData: .single(Tuple(data, nil, .medium, false))) + |> map { $0.2 }) } guard let sharedAccountContext = sharedAccountContext else { diff --git a/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoVisualMediaPaneNode.swift b/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoVisualMediaPaneNode.swift index cc2ff6f4ab..f01a45bf5e 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoVisualMediaPaneNode.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoVisualMediaPaneNode.swift @@ -495,7 +495,7 @@ final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro self.scrollNode.view.delaysContentTouches = false self.scrollNode.view.canCancelContentTouches = true - self.scrollNode.view.showsVerticalScrollIndicator = false + self.scrollNode.view.showsVerticalScrollIndicator = true if #available(iOS 11.0, *) { self.scrollNode.view.contentInsetAdjustmentBehavior = .never } diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoData.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoData.swift index 88d2d9c5a7..15ff1752b1 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoData.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoData.swift @@ -728,20 +728,36 @@ func peerInfoHeaderButtons(peer: Peer?, cachedData: CachedPeerData?, isOpenedFro var displayLeave = !channel.flags.contains(.isCreator) switch channel.info { case .broadcast: - displayLeave = true + if !channel.flags.contains(.isCreator) { + displayLeave = true + } case .group: displayLeave = false if channel.flags.contains(.isCreator) || channel.hasPermission(.inviteMembers) { result.append(.addMember) } } + switch channel.participationStatus { + case .member: + break + default: + displayLeave = false + } result.append(.mute) result.append(.search) if displayLeave { result.append(.leave) } - result.append(.more) + var displayMore = true + if displayLeave && !channel.flags.contains(.isCreator) { + if let adminRights = channel.adminRights, !adminRights.isEmpty { + displayMore = false + } + } + if displayMore { + result.append(.more) + } } else if let group = peer as? TelegramGroup { var canEditGroupInfo = false var canEditMembers = false diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift index fc1cd2f59d..bb2a47f6ea 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift @@ -36,6 +36,7 @@ import LocationResources import LocationUI import Geocoding import TextFormat +import StatisticsUI protocol PeerInfoScreenItem: class { var id: AnyHashable { get } @@ -2215,6 +2216,13 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD } } } else if let channel = peer as? TelegramChannel { + if let cachedData = self.data?.cachedData as? CachedChannelData, cachedData.flags.contains(.canViewStats) { + items.append(ActionSheetButtonItem(title: presentationData.strings.ChannelInfo_Stats, color: .accent, action: { [weak self] in + dismissAction() + self?.openStats() + })) + } + var canReport = true if channel.isVerified { canReport = false @@ -2691,6 +2699,15 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD }) } + private func openStats() { + guard let controller = self.controller, let data = self.data, let peer = data.peer, let cachedData = data.cachedData else { + return + } + self.view.endEditing(true) + + controller.push(channelStatsController(context: self.context, peer: peer, cachedPeerData: cachedData)) + } + private func openReport(user: Bool) { guard let controller = self.controller else { return @@ -3257,7 +3274,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD })) contactsController.navigationPresentation = .modal } else { - contactsController = strongSelf.context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: strongSelf.context, mode: .peerSelection(searchChatList: false, searchGroups: false), options: options, filters: [.excludeSelf, .disable(recentIds)])) + contactsController = strongSelf.context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: strongSelf.context, mode: .peerSelection(searchChatList: false, searchGroups: false, searchChannels: false), options: options, filters: [.excludeSelf, .disable(recentIds)])) contactsController.navigationPresentation = .modal } diff --git a/submodules/TelegramUI/Sources/PeerSelectionControllerNode.swift b/submodules/TelegramUI/Sources/PeerSelectionControllerNode.swift index 45938193a8..8d30cec7a7 100644 --- a/submodules/TelegramUI/Sources/PeerSelectionControllerNode.swift +++ b/submodules/TelegramUI/Sources/PeerSelectionControllerNode.swift @@ -223,7 +223,8 @@ final class PeerSelectionControllerNode: ASDisplayNode { if let requestOpenMessageFromSearch = self?.requestOpenMessageFromSearch { requestOpenMessageFromSearch(peer, messageId) } - }, addContact: nil, peerContextAction: nil), cancel: { [weak self] in + }, addContact: nil, peerContextAction: nil, present: { _ in + }), cancel: { [weak self] in if let requestDeactivateSearch = self?.requestDeactivateSearch { requestDeactivateSearch() } diff --git a/submodules/TelegramUIPreferences/Sources/InAppNotificationSettings.swift b/submodules/TelegramUIPreferences/Sources/InAppNotificationSettings.swift index 3da4f51362..f0cf6f3bfc 100644 --- a/submodules/TelegramUIPreferences/Sources/InAppNotificationSettings.swift +++ b/submodules/TelegramUIPreferences/Sources/InAppNotificationSettings.swift @@ -6,7 +6,7 @@ import SyncCore public enum TotalUnreadCountDisplayStyle: Int32 { case filtered = 0 - var category: ChatListTotalUnreadStateCategory { + public var category: ChatListTotalUnreadStateCategory { switch self { case .filtered: return .filtered @@ -18,7 +18,7 @@ public enum TotalUnreadCountDisplayCategory: Int32 { case chats = 0 case messages = 1 - var statsType: ChatListTotalUnreadStateStats { + public var statsType: ChatListTotalUnreadStateStats { switch self { case .chats: return .chats diff --git a/submodules/WebSearchUI/Sources/LegacyWebSearchGallery.swift b/submodules/WebSearchUI/Sources/LegacyWebSearchGallery.swift index 3a88437893..a645c60264 100644 --- a/submodules/WebSearchUI/Sources/LegacyWebSearchGallery.swift +++ b/submodules/WebSearchUI/Sources/LegacyWebSearchGallery.swift @@ -278,7 +278,7 @@ func legacyWebSearchItem(account: Account, result: ChatContextResult) -> LegacyW |> mapToSignal { value -> Signal in let thumbnailData = value._0 let fullSizeData = value._1 - let fullSizeComplete = value._2 + let fullSizeComplete = value._3 if fullSizeComplete, let data = fullSizeData, let image = UIImage(data: data) { return .single(image)