diff --git a/BUCK b/BUCK index 5122ec3b5a..934c87404c 100644 --- a/BUCK +++ b/BUCK @@ -1,3 +1,7 @@ +load("//Config:utils.bzl", + "library_configs", +) + load("//Config:configs.bzl", "app_binary_configs", "share_extension_configs", @@ -7,7 +11,6 @@ load("//Config:configs.bzl", "intents_extension_configs", "watch_extension_binary_configs", "watch_binary_configs", - "library_configs", "info_plist_substitutions", "app_info_plist_substitutions", "share_extension_info_plist_substitutions", @@ -43,7 +46,9 @@ resource_dependencies = [ "//submodules/LegacyComponents:LegacyComponentsResources", "//submodules/TelegramUI:TelegramUIAssets", "//submodules/TelegramUI:TelegramUIResources", + "//submodules/WalletUI:WalletUIAssets", "//submodules/WalletUI:WalletUIResources", + "//submodules/OverlayStatusController:OverlayStatusControllerResources", "//:AppResources", "//:AppStringResources", "//:InfoPlistStringResources", diff --git a/Config/buck_rule_macros.bzl b/Config/buck_rule_macros.bzl index 609946e1f9..57aad16c62 100644 --- a/Config/buck_rule_macros.bzl +++ b/Config/buck_rule_macros.bzl @@ -1,4 +1,7 @@ -load("//Config:configs.bzl", "library_configs", "dynamic_library_configs", "info_plist_substitutions") +load("//Config:utils.bzl", + "library_configs", + "dynamic_library_configs", +) def apple_lib( name, diff --git a/Config/configs.bzl b/Config/configs.bzl index b52954d5c3..75fa2d15c3 100644 --- a/Config/configs.bzl +++ b/Config/configs.bzl @@ -1,190 +1,25 @@ +load("//Config:utils.bzl", + "config_with_updated_linker_flags", + "configs_with_config", + "merge_dict", + "DEVELOPMENT_LANGUAGE", + "SHARED_CONFIGS", + "ALL_LOAD_LINKER_FLAG", + "read_config_nonempty", + "optimization_config", + "add_provisioning_profile_specifier", + "add_codesign_identity", + "get_build_number", + "get_short_version", + "bundle_identifier", + "get_development_team", + "get_provisioning_profile", + "get_codesign_entitlements", +) -load("//Config:utils.bzl", "config_with_updated_linker_flags", "configs_with_config") -load("//Config:app_configuration.bzl", "appConfig") - -DEVELOPMENT_LANGUAGE = "en" - -def merge_dict(a, b): - d = {} - d.update(a) - d.update(b) - return d - -def pretty(dict, current = ""): - current = "\n" - indent = 0 - for key, value in dict.items(): - current = current + str(key) + ": " - if type(value) == type({}): - current = current + "\n" - indent = 1 - for key2, value2 in value.items(): - current = current + "\t" * indent + str(key2) - current = current + ": " + str(value2) + "\n" - else: - current = current + "\t" * (indent + 1) + str(value) + "\n" - - return current - -SHARED_CONFIGS = { - "IPHONEOS_DEPLOYMENT_TARGET": "9.0", - "SDKROOT": "iphoneos", - "GCC_OPTIMIZATION_LEVEL": "0", - "SWIFT_WHOLE_MODULE_OPTIMIZATION": "NO", - "ONLY_ACTIVE_ARCH": "YES", - "LD_RUNPATH_SEARCH_PATHS": "@executable_path/Frameworks", - "ENABLE_BITCODE": "NO", -} - -def optimization_config(): - return { - "SWIFT_OPTIMIZATION_LEVEL": native.read_config("custom", "optimization"), - } - -# Adding `-all_load` to our binaries works around https://bugs.swift.org/browse/SR-6004. -ALL_LOAD_LINKER_FLAG = "-all_load" - -def read_config_nonempty(name): - value = native.read_config("custom", name) - if value == None: - fail("Configuration parameter custom.%s should be defined" % name) - elif len(value) == 0: - fail("Configuration parameter custom.%s should not be empty" % name) - else: - return value - -def get_codesign_identity(environment): - if environment == "development": - return read_config_nonempty("developmentCodeSignIdentity") - elif environment == "distribution": - return read_config_nonempty("distributionCodeSignIdentity") - else: - fail("Unknown environment " + environment) - -def get_provisioning_profile(environment, type): - if type == "app": - return read_config_nonempty(environment + "ProvisioningProfileApp") - elif type == "share": - return read_config_nonempty(environment + "ProvisioningProfileExtensionShare") - elif type == "widget": - return read_config_nonempty(environment + "ProvisioningProfileExtensionWidget") - elif type == "notification_service": - return read_config_nonempty(environment + "ProvisioningProfileExtensionNotificationService") - elif type == "notification_content": - return read_config_nonempty(environment + "ProvisioningProfileExtensionNotificationContent") - elif type == "intents": - return read_config_nonempty(environment + "ProvisioningProfileExtensionIntents") - elif type == "watch_app": - return read_config_nonempty(environment + "ProvisioningProfileWatchApp") - elif type == "watch_extension": - return read_config_nonempty(environment + "ProvisioningProfileWatchExtension") - else: - fail("Unknown provisioning profile type " + type) - -def get_development_team(): - return read_config_nonempty("developmentTeam") - -def add_item_to_subdict(superdict, key, subkey, item): - subdict = dict(superdict[key]) - subdict[subkey] = item - superdict[key] = subdict - -valid_configurations = ["Debug", "Profile", "Release"] - -def add_provisioning_profile_specifier(configs, type): - for configuration in configs: - if configuration not in valid_configurations: - fail("Unknown configuration " + configuration) - - configs = dict(configs) - for configuration in valid_configurations: - if configuration == "Debug": - add_item_to_subdict(configs, configuration, "PROVISIONING_PROFILE_SPECIFIER", get_provisioning_profile(environment="development", type=type)) - elif configuration == "Profile": - add_item_to_subdict(configs, configuration, "PROVISIONING_PROFILE_SPECIFIER", get_provisioning_profile(environment="development", type=type)) - elif configuration == "Release": - add_item_to_subdict(configs, configuration, "PROVISIONING_PROFILE_SPECIFIER", get_provisioning_profile(environment="distribution", type=type)) - return configs - -def add_codesign_identity(configs): - for configuration in configs: - if configuration not in valid_configurations: - fail("Unknown configuration " + configuration) - - configs = dict(configs) - for configuration in valid_configurations: - if configuration == "Debug": - add_item_to_subdict(configs, configuration, "CODE_SIGN_IDENTITY", get_codesign_identity(environment="development")) - elif configuration == "Profile": - add_item_to_subdict(configs, configuration, "CODE_SIGN_IDENTITY", get_codesign_identity(environment="development")) - elif configuration == "Release": - add_item_to_subdict(configs, configuration, "CODE_SIGN_IDENTITY", get_codesign_identity(environment="distribution")) - return configs - -def get_codesign_entitlements(type): - if type == "app": - return read_config_nonempty("entitlementsApp") - elif type == "share": - return read_config_nonempty("entitlementsExtensionShare") - elif type == "widget": - return read_config_nonempty("entitlementsExtensionWidget") - elif type == "notification_service": - return read_config_nonempty("entitlementsExtensionNotificationService") - elif type == "notification_content": - return read_config_nonempty("entitlementsExtensionNotificationContent") - elif type == "intents": - return read_config_nonempty("entitlementsExtensionIntents") - else: - fail("unknown provisioning profile type") - -def get_build_number(): - return read_config_nonempty("buildNumber") - -def get_short_version(): - return read_config_nonempty("appVersion") - -def bundle_identifier(suffix): - return read_config_nonempty("baseApplicationBundleId") + suffix - -def library_configs(): - lib_specific_config = { - "SWIFT_WHOLE_MODULE_OPTIMIZATION": "NO", - - # Setting SKIP_INSTALL to NO for static library configs would create - # create a generic xcode archive which can not be uploaded the app store - # https://developer.apple.com/library/archive/technotes/tn2215/_index.html - "SKIP_INSTALL": "YES", - } - library_config = merge_dict(SHARED_CONFIGS, lib_specific_config) - library_config = merge_dict(library_config, optimization_config()) - configs = { - "Debug": library_config, - "Profile": library_config, - "Release": library_config, - } - return configs - -def dynamic_library_configs(): - lib_specific_config = { - "SWIFT_WHOLE_MODULE_OPTIMIZATION": "NO", - - # Setting SKIP_INSTALL to NO for static library configs would create - # create a generic xcode archive which can not be uploaded the app store - # https://developer.apple.com/library/archive/technotes/tn2215/_index.html - "SKIP_INSTALL": "YES", - "MACH_O_TYPE": "mh_dylib", - "CODE_SIGNING_ALLOWED": "NO", - } - - library_config = merge_dict(SHARED_CONFIGS, lib_specific_config) - library_config = merge_dict(library_config, optimization_config()) - #library_config = config_with_updated_linker_flags(library_config, ALL_LOAD_LINKER_FLAG) - configs = { - "Debug": library_config, - "Profile": library_config, - "Release": library_config, - } - return configs +load("//Config:app_configuration.bzl", + "appConfig", +) def app_binary_configs(): config = { diff --git a/Config/utils.bzl b/Config/utils.bzl index bb2b6c70f4..ca02712754 100644 --- a/Config/utils.bzl +++ b/Config/utils.bzl @@ -33,3 +33,170 @@ def config_with_updated_linker_flags(config, other_linker_flags, config_key=OTHE new_config[config_key] = '$(inherited) ' + other_linker_flags return new_config + +def merge_dict(a, b): + d = {} + d.update(a) + d.update(b) + return d + +DEVELOPMENT_LANGUAGE = "en" + +SHARED_CONFIGS = { + "IPHONEOS_DEPLOYMENT_TARGET": "9.0", + "SDKROOT": "iphoneos", + "GCC_OPTIMIZATION_LEVEL": "0", + "SWIFT_WHOLE_MODULE_OPTIMIZATION": "NO", + "ONLY_ACTIVE_ARCH": "YES", + "LD_RUNPATH_SEARCH_PATHS": "@executable_path/Frameworks", + "ENABLE_BITCODE": "NO", +} + +# Adding `-all_load` to our binaries works around https://bugs.swift.org/browse/SR-6004. +ALL_LOAD_LINKER_FLAG = "-all_load" + +def optimization_config(): + return { + "SWIFT_OPTIMIZATION_LEVEL": native.read_config("custom", "optimization"), + } + +def read_config_nonempty(name): + value = native.read_config("custom", name) + if value == None: + fail("Configuration parameter custom.%s should be defined" % name) + elif len(value) == 0: + fail("Configuration parameter custom.%s should not be empty" % name) + else: + return value + +def get_codesign_identity(environment): + if environment == "development": + return read_config_nonempty("developmentCodeSignIdentity") + elif environment == "distribution": + return read_config_nonempty("distributionCodeSignIdentity") + else: + fail("Unknown environment " + environment) + +def get_development_team(): + return read_config_nonempty("developmentTeam") + +def add_item_to_subdict(superdict, key, subkey, item): + subdict = dict(superdict[key]) + subdict[subkey] = item + superdict[key] = subdict + +valid_configurations = ["Debug", "Profile", "Release"] + +def add_provisioning_profile_specifier(configs, type): + for configuration in configs: + if configuration not in valid_configurations: + fail("Unknown configuration " + configuration) + + configs = dict(configs) + for configuration in valid_configurations: + if configuration == "Debug": + add_item_to_subdict(configs, configuration, "PROVISIONING_PROFILE_SPECIFIER", get_provisioning_profile(environment="development", type=type)) + elif configuration == "Profile": + add_item_to_subdict(configs, configuration, "PROVISIONING_PROFILE_SPECIFIER", get_provisioning_profile(environment="development", type=type)) + elif configuration == "Release": + add_item_to_subdict(configs, configuration, "PROVISIONING_PROFILE_SPECIFIER", get_provisioning_profile(environment="distribution", type=type)) + return configs + +def add_codesign_identity(configs): + for configuration in configs: + if configuration not in valid_configurations: + fail("Unknown configuration " + configuration) + + configs = dict(configs) + for configuration in valid_configurations: + if configuration == "Debug": + add_item_to_subdict(configs, configuration, "CODE_SIGN_IDENTITY", get_codesign_identity(environment="development")) + elif configuration == "Profile": + add_item_to_subdict(configs, configuration, "CODE_SIGN_IDENTITY", get_codesign_identity(environment="development")) + elif configuration == "Release": + add_item_to_subdict(configs, configuration, "CODE_SIGN_IDENTITY", get_codesign_identity(environment="distribution")) + return configs + +def get_build_number(): + return read_config_nonempty("buildNumber") + +def get_short_version(): + return read_config_nonempty("appVersion") + +def bundle_identifier(suffix): + return read_config_nonempty("baseApplicationBundleId") + suffix + +def library_configs(): + lib_specific_config = { + "SWIFT_WHOLE_MODULE_OPTIMIZATION": "NO", + + # Setting SKIP_INSTALL to NO for static library configs would create + # create a generic xcode archive which can not be uploaded the app store + # https://developer.apple.com/library/archive/technotes/tn2215/_index.html + "SKIP_INSTALL": "YES", + } + library_config = merge_dict(SHARED_CONFIGS, lib_specific_config) + library_config = merge_dict(library_config, optimization_config()) + configs = { + "Debug": library_config, + "Profile": library_config, + "Release": library_config, + } + return configs + +def dynamic_library_configs(): + lib_specific_config = { + "SWIFT_WHOLE_MODULE_OPTIMIZATION": "NO", + + # Setting SKIP_INSTALL to NO for static library configs would create + # create a generic xcode archive which can not be uploaded the app store + # https://developer.apple.com/library/archive/technotes/tn2215/_index.html + "SKIP_INSTALL": "YES", + "MACH_O_TYPE": "mh_dylib", + "CODE_SIGNING_ALLOWED": "NO", + } + + library_config = merge_dict(SHARED_CONFIGS, lib_specific_config) + library_config = merge_dict(library_config, optimization_config()) + configs = { + "Debug": library_config, + "Profile": library_config, + "Release": library_config, + } + return configs + +def get_provisioning_profile(environment, type): + if type == "app": + return read_config_nonempty(environment + "ProvisioningProfileApp") + elif type == "share": + return read_config_nonempty(environment + "ProvisioningProfileExtensionShare") + elif type == "widget": + return read_config_nonempty(environment + "ProvisioningProfileExtensionWidget") + elif type == "notification_service": + return read_config_nonempty(environment + "ProvisioningProfileExtensionNotificationService") + elif type == "notification_content": + return read_config_nonempty(environment + "ProvisioningProfileExtensionNotificationContent") + elif type == "intents": + return read_config_nonempty(environment + "ProvisioningProfileExtensionIntents") + elif type == "watch_app": + return read_config_nonempty(environment + "ProvisioningProfileWatchApp") + elif type == "watch_extension": + return read_config_nonempty(environment + "ProvisioningProfileWatchExtension") + else: + fail("Unknown provisioning profile type " + type) + +def get_codesign_entitlements(type): + if type == "app": + return read_config_nonempty("entitlementsApp") + elif type == "share": + return read_config_nonempty("entitlementsExtensionShare") + elif type == "widget": + return read_config_nonempty("entitlementsExtensionWidget") + elif type == "notification_service": + return read_config_nonempty("entitlementsExtensionNotificationService") + elif type == "notification_content": + return read_config_nonempty("entitlementsExtensionNotificationContent") + elif type == "intents": + return read_config_nonempty("entitlementsExtensionIntents") + else: + fail("unknown provisioning profile type") diff --git a/Config/wallet_configs.bzl b/Config/wallet_configs.bzl new file mode 100644 index 0000000000..50b9fe9d17 --- /dev/null +++ b/Config/wallet_configs.bzl @@ -0,0 +1,58 @@ +load("//Config:utils.bzl", + "config_with_updated_linker_flags", + "configs_with_config", + "merge_dict", + "DEVELOPMENT_LANGUAGE", + "SHARED_CONFIGS", + "ALL_LOAD_LINKER_FLAG", + "optimization_config", + "add_provisioning_profile_specifier", + "add_codesign_identity", + "get_build_number", + "get_short_version", + "bundle_identifier", + "get_development_team", + "get_provisioning_profile", + "get_codesign_entitlements", +) + +load("//Config:app_configuration.bzl", + "appConfig" +) + +def app_binary_configs(): + config = { + "ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES": "YES", + "DEVELOPMENT_LANGUAGE": DEVELOPMENT_LANGUAGE, + "PRODUCT_BUNDLE_IDENTIFIER": bundle_identifier(suffix=""), + "CODE_SIGN_ENTITLEMENTS": get_codesign_entitlements("app"), + "DEVELOPMENT_TEAM": get_development_team(), + "ASSETCATALOG_COMPILER_APPICON_NAME": "AppIconLLC", + "BUILD_NUMBER": get_build_number(), + "PRODUCT_BUNDLE_SHORT_VERSION": get_short_version(), + "APP_NAME": "TON Wallet", + "PRODUCT_NAME": "TON Wallet", + "TARGETED_DEVICE_FAMILY": "1,2", + } + config = merge_dict(SHARED_CONFIGS, config) + config = merge_dict(config, optimization_config()) + config = config_with_updated_linker_flags(config, ALL_LOAD_LINKER_FLAG) + configs = configs_with_config(config) + configs = add_provisioning_profile_specifier(configs, "app") + configs = add_codesign_identity(configs) + return configs + +def app_info_plist_substitutions(): + substitutions = { + "DEVELOPMENT_LANGUAGE": DEVELOPMENT_LANGUAGE, + "EXECUTABLE_NAME": "TON Wallet", + "PRODUCT_BUNDLE_IDENTIFIER": bundle_identifier(suffix=""), + "PRODUCT_NAME": "TON Wallet", + "APP_NAME": "TON Wallet", + "CURRENT_PROJECT_VERSION": "1", + "BUILD_NUMBER": get_build_number(), + "PRODUCT_BUNDLE_SHORT_VERSION": get_short_version(), + "ASSETCATALOG_COMPILER_APPICON_NAME": "AppIconLLC", + "TARGETED_DEVICE_FAMILY": "1,2", + } + return substitutions diff --git a/Makefile b/Makefile index 1d46ca4e7a..0f4fcc01df 100644 --- a/Makefile +++ b/Makefile @@ -50,6 +50,24 @@ BUCK_OPTIONS=\ --config custom.developmentProvisioningProfileWatchExtension="${DEVELOPMENT_PROVISIONING_PROFILE_WATCH_EXTENSION}" \ --config custom.distributionProvisioningProfileWatchExtension="${DISTRIBUTION_PROVISIONING_PROFILE_WATCH_EXTENSION}" +WALLET_BUCK_OPTIONS=\ + --config custom.appVersion="1.0" \ + --config custom.developmentCodeSignIdentity="${DEVELOPMENT_CODE_SIGN_IDENTITY}" \ + --config custom.distributionCodeSignIdentity="${DISTRIBUTION_CODE_SIGN_IDENTITY}" \ + --config custom.developmentTeam="${DEVELOPMENT_TEAM}" \ + --config custom.baseApplicationBundleId="${WALLET_BUNDLE_ID}" \ + --config custom.buildNumber="${BUILD_NUMBER}" \ + --config custom.entitlementsApp="${WALLET_ENTITLEMENTS_APP}" \ + --config custom.developmentProvisioningProfileApp="${WALLET_DEVELOPMENT_PROVISIONING_PROFILE_APP}" \ + --config custom.distributionProvisioningProfileApp="${WALLET_DISTRIBUTION_PROVISIONING_PROFILE_APP}" \ + --config custom.apiId="${API_ID}" \ + --config custom.apiHash="${API_HASH}" \ + --config custom.hockeyAppId="${HOCKEYAPP_ID}" \ + --config custom.isInternalBuild="${IS_INTERNAL_BUILD}" \ + --config custom.isAppStoreBuild="${IS_APPSTORE_BUILD}" \ + --config custom.appStoreId="${APPSTORE_ID}" \ + --config custom.appSpecificUrlScheme="${APP_SPECIFIC_URL_SCHEME}" + BUCK_THREADS_OPTIONS=--config build.threads=$(shell sysctl -n hw.logicalcpu) BUCK_CACHE_OPTIONS= @@ -138,7 +156,7 @@ build_wallet_debug_arm64: check_env //submodules/AsyncDisplayKit:AsyncDisplayKit#shared,iphoneos-arm64 \ //submodules/Display:Display#dwarf-and-dsym,shared,iphoneos-arm64 \ //submodules/Display:Display#shared,iphoneos-arm64 \ - ${BUCK_OPTIONS} ${BUCK_DEBUG_OPTIONS} ${BUCK_THREADS_OPTIONS} ${BUCK_CACHE_OPTIONS} + ${WALLET_BUCK_OPTIONS} ${BUCK_DEBUG_OPTIONS} ${BUCK_THREADS_OPTIONS} ${BUCK_CACHE_OPTIONS} build_debug_armv7: check_env $(BUCK) build \ @@ -392,8 +410,12 @@ project: check_env kill_xcode $(BUCK) project //:workspace --config custom.mode=project ${BUCK_OPTIONS} ${BUCK_DEBUG_OPTIONS} open Telegram_Buck.xcworkspace +wallet_deps: check_env + $(BUCK) query "deps(//Wallet:AppPackage)" \ + ${WALLET_BUCK_OPTIONS} ${BUCK_DEBUG_OPTIONS} + wallet_project: check_env kill_xcode - $(BUCK) project //Wallet:workspace --config custom.mode=project ${BUCK_OPTIONS} ${BUCK_DEBUG_OPTIONS} + $(BUCK) project //Wallet:workspace --config custom.mode=project ${WALLET_BUCK_OPTIONS} ${BUCK_DEBUG_OPTIONS} open Wallet/WalletWorkspace.xcworkspace project_opt: check_env kill_xcode diff --git a/Wallet/BUCK b/Wallet/BUCK index 0e95e9f847..907d1ded34 100644 --- a/Wallet/BUCK +++ b/Wallet/BUCK @@ -1,11 +1,13 @@ -load("//Config:configs.bzl", - "app_binary_configs", +load("//Config:utils.bzl", "library_configs", - "info_plist_substitutions", +) + +load("//Config:wallet_configs.bzl", + "app_binary_configs", + "app_info_plist_substitutions", ) load("//Config:buck_rule_macros.bzl", - "apple_lib", "framework_binary_dependencies", "framework_bundle_dependencies", ) @@ -13,46 +15,25 @@ load("//Config:buck_rule_macros.bzl", framework_dependencies = [ "//submodules/MtProtoKit:MtProtoKit", "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", - "//submodules/Postbox:Postbox", - "//submodules/TelegramCore:TelegramCore", "//submodules/AsyncDisplayKit:AsyncDisplayKit", "//submodules/Display:Display", ] resource_dependencies = [ - "//submodules/TelegramUI:TelegramUIAssets", - "//submodules/TelegramUI:TelegramUIResources", - "//:AppResources", - "//:AppStringResources", - "//:InfoPlistStringResources", - "//:AppIntentVocabularyResources", - "//:Icons", - "//:AdditionalIcons", - "//:LaunchScreen", + "//submodules/WalletUI:WalletUIResources", + "//submodules/WalletUI:WalletUIAssets", + "//submodules/OverlayStatusController:OverlayStatusControllerResources", + ":StringResources", + ":InfoPlistStringResources", + ":Icons", + ":LaunchScreen", ] apple_resource( - name = "AppResources", - files = glob([ - "Telegram-iOS/Resources/**/*", - ], exclude = ["Telegram-iOS/Resources/**/.*"]), - visibility = ["PUBLIC"], -) - -apple_resource( - name = "AppStringResources", + name = "StringResources", files = [], variants = glob([ - "Telegram-iOS/*.lproj/Localizable.strings", - ]), - visibility = ["PUBLIC"], -) - -apple_resource( - name = "AppIntentVocabularyResources", - files = [], - variants = glob([ - "Telegram-iOS/*.lproj/AppIntentVocabulary.plist", + "Strings/*.lproj/Localizable.strings", ]), visibility = ["PUBLIC"], ) @@ -61,7 +42,7 @@ apple_resource( name = "InfoPlistStringResources", files = [], variants = glob([ - "Telegram-iOS/*.lproj/InfoPlist.strings", + "InfoPlistStrings/*.lproj/InfoPlist.strings", ]), visibility = ["PUBLIC"], ) @@ -69,25 +50,16 @@ apple_resource( apple_asset_catalog( name = "Icons", dirs = [ - "Telegram-iOS/Icons.xcassets", - "Telegram-iOS/AppIcons.xcassets", + "Icons.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", + "LaunchScreen.xib", ], visibility = ["PUBLIC"], ) @@ -95,8 +67,7 @@ apple_resource( apple_library( name = "AppLibrary", visibility = [ - "//:", - "//...", + "//Wallet:...", ], configs = library_configs(), swift_version = native.read_config("swift", "version"), @@ -110,6 +81,7 @@ apple_library( ], deps = [ "//submodules/WalletUI:WalletUI", + "//submodules/BuildConfig:BuildConfig", ] + framework_binary_dependencies(framework_dependencies), frameworks = [ @@ -122,8 +94,7 @@ apple_library( apple_binary( name = "AppBinary", visibility = [ - "//:", - "//...", + "//Wallet:...", ], configs = app_binary_configs(), swift_version = native.read_config("swift", "version"), diff --git a/Wallet/Icons.xcassets/AppIconLLC.appiconset/BlueIcon@2x-1.png b/Wallet/Icons.xcassets/AppIconLLC.appiconset/BlueIcon@2x-1.png new file mode 100644 index 0000000000..dd360d8f50 Binary files /dev/null and b/Wallet/Icons.xcassets/AppIconLLC.appiconset/BlueIcon@2x-1.png differ diff --git a/Wallet/Icons.xcassets/AppIconLLC.appiconset/BlueIcon@2x.png b/Wallet/Icons.xcassets/AppIconLLC.appiconset/BlueIcon@2x.png new file mode 100644 index 0000000000..2e502e7dab Binary files /dev/null and b/Wallet/Icons.xcassets/AppIconLLC.appiconset/BlueIcon@2x.png differ diff --git a/Wallet/Icons.xcassets/AppIconLLC.appiconset/BlueIcon@3x.png b/Wallet/Icons.xcassets/AppIconLLC.appiconset/BlueIcon@3x.png new file mode 100644 index 0000000000..c47aeed4b1 Binary files /dev/null and b/Wallet/Icons.xcassets/AppIconLLC.appiconset/BlueIcon@3x.png differ diff --git a/Wallet/Icons.xcassets/AppIconLLC.appiconset/BlueIconIpad.png b/Wallet/Icons.xcassets/AppIconLLC.appiconset/BlueIconIpad.png new file mode 100644 index 0000000000..5eaf0bab23 Binary files /dev/null and b/Wallet/Icons.xcassets/AppIconLLC.appiconset/BlueIconIpad.png differ diff --git a/Wallet/Icons.xcassets/AppIconLLC.appiconset/BlueIconIpad@2x.png b/Wallet/Icons.xcassets/AppIconLLC.appiconset/BlueIconIpad@2x.png new file mode 100644 index 0000000000..6d9e7ab98c Binary files /dev/null and b/Wallet/Icons.xcassets/AppIconLLC.appiconset/BlueIconIpad@2x.png differ diff --git a/Wallet/Icons.xcassets/AppIconLLC.appiconset/BlueIconLargeIpad@2x.png b/Wallet/Icons.xcassets/AppIconLLC.appiconset/BlueIconLargeIpad@2x.png new file mode 100644 index 0000000000..9bf363744d Binary files /dev/null and b/Wallet/Icons.xcassets/AppIconLLC.appiconset/BlueIconLargeIpad@2x.png differ diff --git a/Wallet/Icons.xcassets/AppIconLLC.appiconset/BlueNotificationIcon.png b/Wallet/Icons.xcassets/AppIconLLC.appiconset/BlueNotificationIcon.png new file mode 100644 index 0000000000..dc5916282e Binary files /dev/null and b/Wallet/Icons.xcassets/AppIconLLC.appiconset/BlueNotificationIcon.png differ diff --git a/Wallet/Icons.xcassets/AppIconLLC.appiconset/BlueNotificationIcon@2x-1.png b/Wallet/Icons.xcassets/AppIconLLC.appiconset/BlueNotificationIcon@2x-1.png new file mode 100644 index 0000000000..0898af42d9 Binary files /dev/null and b/Wallet/Icons.xcassets/AppIconLLC.appiconset/BlueNotificationIcon@2x-1.png differ diff --git a/Wallet/Icons.xcassets/AppIconLLC.appiconset/BlueNotificationIcon@2x.png b/Wallet/Icons.xcassets/AppIconLLC.appiconset/BlueNotificationIcon@2x.png new file mode 100644 index 0000000000..0898af42d9 Binary files /dev/null and b/Wallet/Icons.xcassets/AppIconLLC.appiconset/BlueNotificationIcon@2x.png differ diff --git a/Wallet/Icons.xcassets/AppIconLLC.appiconset/BlueNotificationIcon@3x.png b/Wallet/Icons.xcassets/AppIconLLC.appiconset/BlueNotificationIcon@3x.png new file mode 100644 index 0000000000..f7725e9914 Binary files /dev/null and b/Wallet/Icons.xcassets/AppIconLLC.appiconset/BlueNotificationIcon@3x.png differ diff --git a/Wallet/Icons.xcassets/AppIconLLC.appiconset/Contents.json b/Wallet/Icons.xcassets/AppIconLLC.appiconset/Contents.json new file mode 100644 index 0000000000..00dd28927f --- /dev/null +++ b/Wallet/Icons.xcassets/AppIconLLC.appiconset/Contents.json @@ -0,0 +1,119 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "BlueNotificationIcon@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "BlueNotificationIcon@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Simple@58x58.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Simple@87x87.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Simple@80x80.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "BlueIcon@2x-1.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "BlueIcon@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "BlueIcon@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "BlueNotificationIcon.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "BlueNotificationIcon@2x-1.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Simple@29x29.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Simple@58x58-1.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Simple@40x40-1.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Simple@80x80-1.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "BlueIconIpad.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "BlueIconIpad@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "BlueIconLargeIpad@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Simple-iTunesArtwork.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "pre-rendered" : true + } +} \ No newline at end of file diff --git a/Wallet/Icons.xcassets/AppIconLLC.appiconset/Simple-iTunesArtwork.png b/Wallet/Icons.xcassets/AppIconLLC.appiconset/Simple-iTunesArtwork.png new file mode 100644 index 0000000000..f00a2857f0 Binary files /dev/null and b/Wallet/Icons.xcassets/AppIconLLC.appiconset/Simple-iTunesArtwork.png differ diff --git a/Wallet/Icons.xcassets/AppIconLLC.appiconset/Simple@29x29.png b/Wallet/Icons.xcassets/AppIconLLC.appiconset/Simple@29x29.png new file mode 100644 index 0000000000..90d7b67bc0 Binary files /dev/null and b/Wallet/Icons.xcassets/AppIconLLC.appiconset/Simple@29x29.png differ diff --git a/Wallet/Icons.xcassets/AppIconLLC.appiconset/Simple@40x40-1.png b/Wallet/Icons.xcassets/AppIconLLC.appiconset/Simple@40x40-1.png new file mode 100644 index 0000000000..a79cb5dcdc Binary files /dev/null and b/Wallet/Icons.xcassets/AppIconLLC.appiconset/Simple@40x40-1.png differ diff --git a/Wallet/Icons.xcassets/AppIconLLC.appiconset/Simple@58x58-1.png b/Wallet/Icons.xcassets/AppIconLLC.appiconset/Simple@58x58-1.png new file mode 100644 index 0000000000..aa6a4a442e Binary files /dev/null and b/Wallet/Icons.xcassets/AppIconLLC.appiconset/Simple@58x58-1.png differ diff --git a/Wallet/Icons.xcassets/AppIconLLC.appiconset/Simple@58x58.png b/Wallet/Icons.xcassets/AppIconLLC.appiconset/Simple@58x58.png new file mode 100644 index 0000000000..aa6a4a442e Binary files /dev/null and b/Wallet/Icons.xcassets/AppIconLLC.appiconset/Simple@58x58.png differ diff --git a/Wallet/Icons.xcassets/AppIconLLC.appiconset/Simple@80x80-1.png b/Wallet/Icons.xcassets/AppIconLLC.appiconset/Simple@80x80-1.png new file mode 100644 index 0000000000..385bc474b2 Binary files /dev/null and b/Wallet/Icons.xcassets/AppIconLLC.appiconset/Simple@80x80-1.png differ diff --git a/Wallet/Icons.xcassets/AppIconLLC.appiconset/Simple@80x80.png b/Wallet/Icons.xcassets/AppIconLLC.appiconset/Simple@80x80.png new file mode 100644 index 0000000000..385bc474b2 Binary files /dev/null and b/Wallet/Icons.xcassets/AppIconLLC.appiconset/Simple@80x80.png differ diff --git a/Wallet/Icons.xcassets/AppIconLLC.appiconset/Simple@87x87.png b/Wallet/Icons.xcassets/AppIconLLC.appiconset/Simple@87x87.png new file mode 100644 index 0000000000..c0a9ce9319 Binary files /dev/null and b/Wallet/Icons.xcassets/AppIconLLC.appiconset/Simple@87x87.png differ diff --git a/Wallet/Icons.xcassets/Contents.json b/Wallet/Icons.xcassets/Contents.json new file mode 100644 index 0000000000..da4a164c91 --- /dev/null +++ b/Wallet/Icons.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Wallet/Info.plist b/Wallet/Info.plist index d8ada8d0f8..1dcbb9f34b 100644 --- a/Wallet/Info.plist +++ b/Wallet/Info.plist @@ -12,166 +12,20 @@ $(EXECUTABLE_NAME) CFBundleIcons - CFBundleAlternateIcons - - Black - - CFBundleIconFiles - - BlackIcon - BlackNotificationIcon - - UIPrerenderedIcon - - - BlackClassic - - CFBundleIconFiles - - BlackClassicIcon - BlackClassicNotificationIcon - - UIPrerenderedIcon - - - BlackFilled - - CFBundleIconFiles - - BlackFilledIcon - - UIPrerenderedIcon - - - Blue - - CFBundleIconFiles - - BlueIcon - BlueNotificationIcon - - UIPrerenderedIcon - - - BlueClassic - - CFBundleIconFiles - - BlueClassicIcon - BlueClassicNotificationIcon - - UIPrerenderedIcon - - - BlueFilled - - CFBundleIconFiles - - BlueFilledIcon - - UIPrerenderedIcon - - - WhiteFilled - - CFBundleIconFiles - - WhiteFilledIcon - - UIPrerenderedIcon - - - CFBundlePrimaryIcon CFBundleIconName - AppIconLLC + AppIconWallet UIPrerenderedIcon CFBundleIcons~ipad - CFBundleAlternateIcons - - Black - - CFBundleIconFiles - - BlackIconIpad - BlackIconLargeIpad - BlackNotificationIcon - - UIPrerenderedIcon - - - BlackClassic - - CFBundleIconFiles - - BlackClassicIconIpad - BlackClassicIconLargeIpad - BlackClassicNotificationIcon - - UIPrerenderedIcon - - - BlackFilled - - CFBundleIconFiles - - BlackFilledIconIpad - BlackFilledIconLargeIpad - - UIPrerenderedIcon - - - Blue - - CFBundleIconFiles - - BlueIconIpad - BlueIconLargeIpad - BlueNotificationIcon - - UIPrerenderedIcon - - - BlueClassic - - CFBundleIconFiles - - BlueClassicIconIpad - BlueClassicIconLargeIpad - BlueClassicNotificationIcon - - UIPrerenderedIcon - - - BlueFilled - - CFBundleIconFiles - - BlueFilledIconIpad - BlueFilledIconLargeIpad - - UIPrerenderedIcon - - - WhiteFilled - - CFBundleIconFiles - - WhiteFilledIcon - - UIPrerenderedIcon - - - CFBundlePrimaryIcon CFBundleIconName - AppIconLLC + AppIconWallet UIPrerenderedIcon @@ -190,27 +44,6 @@ ???? CFBundleURLTypes - - CFBundleTypeRole - Viewer - CFBundleURLName - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleURLSchemes - - telegram - - - - CFBundleTypeRole - Viewer - CFBundleURLName - $(PRODUCT_BUNDLE_IDENTIFIER).compatibility - CFBundleURLSchemes - - tg - $(APP_SPECIFIC_URL_SCHEME) - - CFBundleTypeRole Viewer @@ -226,38 +59,6 @@ ${BUILD_NUMBER} ITSAppUsesNonExemptEncryption - LSApplicationQueriesSchemes - - instagram - comgooglemaps-x-callback - foursquare - here-location - yandexmaps - yandexnavi - comgooglemaps - youtube - twitter - vk - waze - googlechrome - googlechromes - firefox - touch-http - touch-https - yandexbrowser-open-url - vimeo - vine - coub - uber - citymapper - lyft - opera-http - opera-https - firefox-focus - ddgQuickLink - moovit - alook - LSRequiresIPhoneOS NSAppTransportSecurity @@ -265,43 +66,8 @@ NSAllowsArbitraryLoads - NSCameraUsageDescription - We need this so that you can take and share photos and videos. - NSContactsUsageDescription - Telegram stores your contacts heavily encrypted in the cloud to let you connect with your friends across all your devices. NSFaceIDUsageDescription - You can use Face ID to unlock the app. - NSLocationAlwaysUsageDescription - When you send your location to your friends, Telegram needs access to show them a map. You also need this to send locations from an Apple Watch. - NSLocationWhenInUseUsageDescription - When you send your location to your friends, Telegram needs access to show them a map. - NSMicrophoneUsageDescription - We need this so that you can record and share voice messages and videos with sound. - NSMotionUsageDescription - When you send your location to your friends, Telegram needs access to show them a map. - NSPhotoLibraryAddUsageDescription - We need this so that you can share photos and videos from your photo library. - NSPhotoLibraryUsageDescription - We need this so that you can share photos and videos from your photo library. - NSSiriUsageDescription - You can use Siri to send messages. - NSUserActivityTypes - - INSendMessageIntent - RemindAboutChatIntent - - UIAppFonts - - SFCompactRounded-Semibold.otf - - UIBackgroundModes - - audio - fetch - location - remote-notification - voip - + For better security, please allow TON Wallet to use your Face ID to authenticate payments. UIDeviceFamily 1 @@ -338,29 +104,5 @@ UIViewGroupOpacity - UTImportedTypeDeclarations - - - UTTypeConformsTo - - public.data - - UTTypeDescription - Telegram iOS Color Theme File - UTTypeIconFiles - - BlueIcon@3x.png - - UTTypeIdentifier - org.telegram.Telegram-iOS.theme - UTTypeTagSpecification - - public.filename-extension - - tgios-theme - - - - diff --git a/Wallet/InfoPlistStrings/en.lproj/InfoPlist.strings b/Wallet/InfoPlistStrings/en.lproj/InfoPlist.strings new file mode 100644 index 0000000000..1fc9a923ce --- /dev/null +++ b/Wallet/InfoPlistStrings/en.lproj/InfoPlist.strings @@ -0,0 +1,5 @@ +/* Localized versions of Info.plist keys */ + +"NSCameraUsageDescription" = "We need this so that you can take and share photos and videos."; +"NSPhotoLibraryUsageDescription" = "We need this so that you can share photos and videos from your photo library."; +"NSFaceIDUsageDescription" = "For better security, please allow TON Wallet to use your Face ID to authenticate payments."; diff --git a/Wallet/LaunchScreen.xib b/Wallet/LaunchScreen.xib new file mode 100644 index 0000000000..9584dae552 --- /dev/null +++ b/Wallet/LaunchScreen.xib @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/Wallet/Sources/AppDelegate.swift b/Wallet/Sources/AppDelegate.swift index 9fb7caa44a..bcffbee986 100644 --- a/Wallet/Sources/AppDelegate.swift +++ b/Wallet/Sources/AppDelegate.swift @@ -1,8 +1,9 @@ import UIKit import Display -import TelegramPresentationData -import WalletUI import SwiftSignalKit +import BuildConfig +import WalletUI +import WalletCore private func encodeText(_ string: String, _ key: Int) -> String { var result = "" @@ -119,10 +120,166 @@ private class ApplicationStatusBarHost: StatusBarHost { } } +private let records = Atomic<[WalletStateRecord]>(value: []) + +private final class WalletStorageInterfaceImpl: WalletStorageInterface { + func watchWalletRecords() -> Signal<[WalletStateRecord], NoError> { + return .single(records.with { $0 }) + } + + func getWalletRecords() -> Signal<[WalletStateRecord], NoError> { + return .single(records.with { $0 }) + } + + func updateWalletRecords(_ f: @escaping ([WalletStateRecord]) -> [WalletStateRecord]) -> Signal<[WalletStateRecord], NoError> { + return .single(records.modify(f)) + } +} + +private final class WalletContextImpl: WalletContext { + let storage: WalletStorageInterface + let tonInstance: TonInstance + let keychain: TonKeychain + let presentationData: WalletPresentationData + + var inForeground: Signal { + return .single(true) + } + + func getServerSalt() -> Signal { + return .single(Data()) + } + + func presentNativeController(_ controller: UIViewController) { + + } + + func idleTimerExtension() -> Disposable { + return EmptyDisposable + } + + func openUrl(_ url: String) { + + } + + func shareUrl(_ url: String) { + + } + + func openPlatformSettings() { + + } + + func authorizeAccessToCamera(completion: @escaping () -> Void) { + completion() + } + + func pickImage(completion: @escaping (UIImage) -> Void) { + } + + init(basePath: String, config: String, blockchainName: String, navigationBarTheme: NavigationBarTheme) { + self.storage = WalletStorageInterfaceImpl() + self.tonInstance = TonInstance( + basePath: basePath, + config: config, + blockchainName: blockchainName, + proxy: nil + ) + self.keychain = TonKeychain(encryptionPublicKey: { + return .single(Data()) + }, encrypt: { data in + return .single(TonKeychainEncryptedData(publicKey: Data(), data: data)) + }, decrypt: { data in + return .single(data.data) + }) + let accentColor = UIColor(rgb: 0x007ee5) + self.presentationData = WalletPresentationData( + theme: WalletTheme( + info: WalletInfoTheme( + incomingFundsTitleColor: UIColor(rgb: 0x00b12c), + outgoingFundsTitleColor: UIColor(rgb: 0xff3b30) + ), setup: WalletSetupTheme( + buttonFillColor: accentColor, + buttonForegroundColor: .white, + inputBackgroundColor: UIColor(rgb: 0xe9e9e9), + inputPlaceholderColor: UIColor(rgb: 0x818086), + inputTextColor: .black, + inputClearButtonColor: UIColor(rgb: 0x7b7b81).withAlphaComponent(0.8) + ), + list: WalletListTheme( + itemPrimaryTextColor: .black, + itemSecondaryTextColor: UIColor(rgb: 0x8e8e93), + itemPlaceholderTextColor: UIColor(rgb: 0xc8c8ce), + itemDestructiveColor: UIColor(rgb: 0xff3b30), + itemAccentColor: accentColor, + itemDisabledTextColor: UIColor(rgb: 0x8e8e93), + plainBackgroundColor: .white, + blocksBackgroundColor: UIColor(rgb: 0xefeff4), + itemPlainSeparatorColor: UIColor(rgb: 0xc8c7cc), + itemBlocksBackgroundColor: .white, + itemBlocksSeparatorColor: UIColor(rgb: 0xc8c7cc), + itemHighlightedBackgroundColor: UIColor(rgb: 0xe5e5ea), + sectionHeaderTextColor: UIColor(rgb: 0x6d6d72), + freeTextColor: UIColor(rgb: 0x6d6d72), + freeTextErrorColor: UIColor(rgb: 0xcf3030), + inputClearButtonColor: UIColor(rgb: 0xcccccc) + ), + statusBarStyle: .Black, + navigationBar: navigationBarTheme, + keyboardAppearance: .light, + alert: AlertControllerTheme( + backgroundType: .light, + backgroundColor: .white, + separatorColor: UIColor(white: 0.9, alpha: 1.0), + highlightedItemColor: UIColor(rgb: 0xe5e5ea), + primaryColor: .black, + secondaryColor: UIColor(rgb: 0x5e5e5e), + accentColor: accentColor, + destructiveColor: UIColor(rgb: 0xff3b30), + disabledColor: UIColor(rgb: 0xd0d0d0) + ), + actionSheet: ActionSheetControllerTheme( + dimColor: UIColor(white: 0.0, alpha: 0.4), + backgroundType: .light, + itemBackgroundColor: .white, + itemHighlightedBackgroundColor: UIColor(white: 0.9, alpha: 1.0), + standardActionTextColor: accentColor, + destructiveActionTextColor: UIColor(rgb: 0xff3b30), + disabledActionTextColor: UIColor(rgb: 0xb3b3b3), + primaryTextColor: .black, + secondaryTextColor: UIColor(rgb: 0x5e5e5e), + controlAccentColor: accentColor, + controlColor: UIColor(rgb: 0x7e8791), + switchFrameColor: UIColor(rgb: 0xe0e0e0), + switchContentColor: UIColor(rgb: 0x77d572), + switchHandleColor: UIColor(rgb: 0xffffff) + ) + ), strings: WalletStrings( + primaryComponent: WalletStringsComponent( + languageCode: "en", + localizedName: "English", + pluralizationRulesCode: "en", + dict: [:] + ), + secondaryComponent: nil, + groupingSeparator: " " + ), dateTimeFormat: WalletPresentationDateTimeFormat( + timeFormat: .regular, + dateFormat: .dayFirst, + dateSeparator: ".", + decimalSeparator: ".", + groupingSeparator: " " + ) + ) + } +} + @objc(AppDelegate) final class AppDelegate: NSObject, UIApplicationDelegate { var window: UIWindow? - var mainWindow: Window1? + + private var mainWindow: Window1? + private var walletContext: WalletContextImpl? func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { let statusBarHost = ApplicationStatusBarHost() @@ -131,11 +288,61 @@ final class AppDelegate: NSObject, UIApplicationDelegate { hostView.containerView.backgroundColor = UIColor.white self.window = window - let navigationController = NavigationController(mode: .single, theme: NavigationControllerTheme(presentationTheme: defaultPresentationTheme), backgroundDetailsMode: nil) - navigationController.setViewControllers([], animated: false) + let navigationBarTheme = NavigationBarTheme( + buttonColor: .blue, + disabledButtonColor: .gray, + primaryTextColor: .black, + backgroundColor: .lightGray, + separatorColor: .black, + badgeBackgroundColor: .red, + badgeStrokeColor: .red, + badgeTextColor: .white + ) + + let navigationController = NavigationController( + mode: .single, + theme: NavigationControllerTheme( + statusBar: .black, + navigationBar: navigationBarTheme, + emptyAreaColor: .white + ), backgroundDetailsMode: nil + ) + + let documentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] + + let config = +""" +{ + "liteservers": [ + { + "ip": 1137658550, + "port": 4924, + "id": { + "@type": "pub.ed25519", + "key": "peJTw/arlRfssgTuf9BMypJzqOi7SXEqSPSWiEw2U1M=" + } + } + ], + "validator": { + "@type": "validator.config.global", + "zero_state": { + "workchain": -1, + "shard": -9223372036854775808, + "seqno": 0, + "root_hash": "VCSXxDHhTALFxReyTZRd8E4Ya3ySOmpOWAS4rBX9XBY=", + "file_hash": "eh9yveSz1qMdJ7mOsO+I+H77jkLr9NpAuEkoJuseXBo=" + } + } +} +""" + + let walletContext = WalletContextImpl(basePath: documentsPath, config: config, blockchainName: "testnet", navigationBarTheme: navigationBarTheme) + self.walletContext = walletContext + + let splashScreen = WalletSplashScreen(context: walletContext, mode: .intro, walletCreatedPreloadState: nil) + + navigationController.setViewControllers([splashScreen], animated: false) self.mainWindow?.viewController = navigationController - navigationController.presentOverlay(controller: standardTextAlertController(theme: AlertControllerTheme(presentationTheme: defaultPresentationTheme), title: "Test", text: "Text", actions: [TextAlertAction(type: .defaultAction, title: "OK", action: { - })]), inGlobal: false) self.window?.makeKeyAndVisible() diff --git a/Wallet/Strings/ar.lproj/Localizable.strings b/Wallet/Strings/ar.lproj/Localizable.strings new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Wallet/Strings/ca.lproj/Localizable.strings b/Wallet/Strings/ca.lproj/Localizable.strings new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Wallet/Strings/de.lproj/Localizable.strings b/Wallet/Strings/de.lproj/Localizable.strings new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Wallet/Strings/en.lproj/Localizable.strings b/Wallet/Strings/en.lproj/Localizable.strings new file mode 100644 index 0000000000..9697a8a2ba --- /dev/null +++ b/Wallet/Strings/en.lproj/Localizable.strings @@ -0,0 +1,193 @@ +"Wallet.Updated.JustNow" = "just now"; +"Wallet.Updated.MinutesAgo_0" = "%@ minutes ago"; //three to ten +"Wallet.Updated.MinutesAgo_1" = "1 minute ago"; //one +"Wallet.Updated.MinutesAgo_2" = "2 minutes ago"; //two +"Wallet.Updated.MinutesAgo_3_10" = "%@ minutes ago"; //three to ten +"Wallet.Updated.MinutesAgo_many" = "%@ minutes ago"; // more than ten +"Wallet.Updated.MinutesAgo_any" = "%@ minutes ago"; // more than ten +"Wallet.Updated.HoursAgo_0" = "%@ hours ago"; +"Wallet.Updated.HoursAgo_1" = "1 hour ago"; +"Wallet.Updated.HoursAgo_2" = "2 hours ago"; +"Wallet.Updated.HoursAgo_3_10" = "%@ hours ago"; +"Wallet.Updated.HoursAgo_any" = "%@ hours ago"; +"Wallet.Updated.HoursAgo_many" = "%@ hours ago"; +"Wallet.Updated.HoursAgo_0" = "%@ hours ago"; +"Wallet.Updated.YesterdayAt" = "yesterday at %@"; +"Wallet.Updated.AtDate" = "%@"; +"Wallet.Updated.TodayAt" = "today at %@"; +"Wallet.Info.WalletCreated" = "Wallet Created"; +"Wallet.Info.Address" = "Your wallet address"; +"Wallet.Info.YourBalance" = "your balance"; +"Wallet.Info.Receive" = "Receive"; +"Wallet.Info.Send" = "Send"; +"Wallet.Info.RefreshErrorTitle" = "No network"; +"Wallet.Info.RefreshErrorText" = "Couldn't refresh balance. Please make sure your internet connection is working and try again."; +"Wallet.Info.RefreshErrorNetworkText" = "The wallet state can not be retrieved at this time. Please try again later."; +"Wallet.Info.UnknownTransaction" = "Empty Transaction"; +"Wallet.Info.TransactionTo" = "to"; +"Wallet.Info.TransactionFrom" = "from"; +"Wallet.Info.Updating" = "updating"; +"Wallet.Info.TransactionBlockchainFee" = "%@ blockchain fee"; +"Wallet.Info.TransactionPendingHeader" = "Pending"; +"Wallet.Qr.ScanCode" = "Scan QR Code"; +"Wallet.Qr.Title" = "QR Code"; +"Wallet.Receive.Title" = "Receive Grams"; +"Wallet.Receive.AddressHeader" = "YOUR WALLET ADDRESS"; +"Wallet.Receive.InvoiceUrlHeader" = "INVOICE URL"; +"Wallet.Receive.CopyAddress" = "Copy Wallet Address"; +"Wallet.Receive.CopyInvoiceUrl" = "Copy Invoice URL"; +"Wallet.Receive.ShareAddress" = "Share Wallet Address"; +"Wallet.Receive.ShareInvoiceUrl" = "Share Invoice URL"; +"Wallet.Receive.ShareUrlInfo" = "Share this link with other Gram wallet owners to receive Grams from them."; +"Wallet.Receive.AmountHeader" = "AMOUNT"; +"Wallet.Receive.AmountText" = "Grams to receive"; +"Wallet.Receive.AmountInfo" = "You can specify amount and purpose of the payment to save the sender some time."; +"Wallet.Receive.CommentHeader" = "COMMENT (OPTIONAL)"; +"Wallet.Receive.CommentInfo" = "Description of the payment"; +"Wallet.Receive.AddressCopied" = "Address copied to clipboard."; +"Wallet.Receive.InvoiceUrlCopied" = "Invoice URL copied to clipboard."; +"Wallet.Send.Title" = "Send Grams"; +"Wallet.Send.AddressHeader" = "RECIPIENT WALLET ADDRESS"; +"Wallet.Send.AddressText" = "Enter wallet address..."; +"Wallet.Send.AddressInfo" = "Paste the 48-letter address of the recipient here or ask them to send you a ton:// link."; +"Wallet.Send.Balance" = "Balance: %@"; +"Wallet.Send.AmountText" = "Grams to send"; +"Wallet.Send.Confirmation" = "Confirmation"; +"Wallet.Send.ConfirmationText" = "Do you want to send **%1$@** Grams to\n%2$@?"; +"Wallet.Send.ConfirmationConfirm" = "Confirm"; +"Wallet.Send.Send" = "Send"; +"Wallet.Settings.Title" = "Wallet Settings"; +"Wallet.Settings.DeleteWallet" = "Delete Wallet"; +"Wallet.Settings.DeleteWalletInfo" = "This will disconnect the wallet from this app. You will be able to restore your wallet using 24 secret words – or import another wallet.\n\nWallets are located in the TON Blockchain, which is not controlled by Telegram. If you want a wallet to be deleted, simply transfer all the grams from it and leave it empty."; +"Wallet.Intro.NotNow" = "Not Now"; +"Wallet.Intro.ImportExisting" = "Import existing wallet"; +"Wallet.Intro.CreateErrorTitle" = "An Error Occurred"; +"Wallet.Intro.CreateErrorText" = "Sorry. Please try again."; +"Wallet.Intro.Title" = "Gram Wallet"; +"Wallet.Intro.Text" = "The gram wallet allows you to make fast and secure blockchain-based payments without intermediaries."; +"Wallet.Intro.CreateWallet" = "Create My Wallet"; +"Wallet.Intro.Terms" = "By creating a wallet you accept the\n[Terms of Conditions]()."; +"Wallet.Intro.TermsUrl" = "https://telegram.org/tos/wallet"; +"Wallet.Created.Title" = "Congratulations"; +"Wallet.Created.Text" = "Your Gram wallet has just been created. Only you control it.\n\nTo be able to always have access to it, please write down secret words and\nset up a secure passcode."; +"Wallet.Created.Proceed" = "Proceed"; +"Wallet.Created.ExportErrorTitle" = "Error"; +"Wallet.Created.ExportErrorText" = "Encryption error. Please make sure you have enabled a device passcode in iOS settings and try again."; +"Wallet.Completed.Title" = "Ready to go!"; +"Wallet.Completed.Text" = "You’re all set. Now you have a wallet that only you control - directly, without middlemen or bankers."; +"Wallet.Completed.ViewWallet" = "View My Wallet"; +"Wallet.RestoreFailed.Title" = "Too Bad"; +"Wallet.RestoreFailed.Text" = "Without the secret words, you can't\nrestore access to your wallet."; +"Wallet.RestoreFailed.CreateWallet" = "Create a New Wallet"; +"Wallet.RestoreFailed.EnterWords" = "Enter 24 words"; +"Wallet.Sending.Title" = "Sending Grams"; +"Wallet.Sending.Text" = "Please wait a few seconds for your transaction to be processed..."; +"Wallet.Sending.Title" = "Sending Grams"; +"Wallet.Sent.Title" = "Done!"; +"Wallet.Sent.Text" = "**%@ Grams** have been sent."; +"Wallet.Sent.ViewWallet" = "View My Wallet"; +"Wallet.SecureStorageNotAvailable.Title" = "Set a Passcode"; +"Wallet.SecureStorageNotAvailable.Text" = "Please set up a Passcode on your device to enable secure payments with your Gram wallet."; +"Wallet.SecureStorageReset.Title" = "Security Settings Have Changed"; +"Wallet.SecureStorageReset.BiometryTouchId" = "Touch ID"; +"Wallet.SecureStorageReset.BiometryFaceId" = "Face ID"; +"Wallet.SecureStorageReset.BiometryText" = "Unfortunately, your wallet is no longer available because your system Passcode or %@ has been turned off. Please enable them before proceeding."; +"Wallet.SecureStorageReset.PasscodeText" = "Unfortunately, your wallet is no longer available because your system Passcode has been turned off. Please enable it before proceeding."; +"Wallet.SecureStorageChanged.BiometryText" = "Unfortunately, your wallet is no longer available due to the change in your system security settings (Passcode/%@). To restore your wallet, tap \"Import existing wallet\"."; +"Wallet.SecureStorageChanged.PasscodeText" = "Unfortunately, your wallet is no longer available due to the change in your system security settings (Passcode). To restore your wallet, tap \"Import existing wallet\"."; +"Wallet.SecureStorageChanged.ImportWallet" = "Import Existing Wallet"; +"Wallet.SecureStorageChanged.CreateWallet" = "Create New Wallet"; +"Wallet.TransactionInfo.Title" = "Transaction"; +"Wallet.TransactionInfo.NoAddress" = "No Address"; +"Wallet.TransactionInfo.RecipientHeader" = "RECIPIENT"; +"Wallet.TransactionInfo.SenderHeader" = "SENDER"; +"Wallet.TransactionInfo.CopyAddress" = "Copy Wallet Address"; +"Wallet.TransactionInfo.AddressCopied" = "Address copied to clipboard."; +"Wallet.TransactionInfo.SendGrams" = "Send Grams"; +"Wallet.TransactionInfo.CommentHeader" = "COMMENT"; +"Wallet.TransactionInfo.StorageFeeHeader" = "STORAGE FEE"; +"Wallet.TransactionInfo.OtherFeeHeader" = "TRANSACTION FEE"; +"Wallet.TransactionInfo.StorageFeeInfo" = "Blockchain validators collect a tiny fee for storing information about your decentralized wallet. [More info]()"; +"Wallet.TransactionInfo.OtherFeeInfo" = "Blockchain validators collect a tiny fee for processing your decentralized transactions. [More info]()"; +"Wallet.TransactionInfo.FeeInfoURL" = "https://telegram.org/wallet/fee"; +"Wallet.WordCheck.Title" = "Test Time!"; +"Wallet.WordCheck.Text" = "Let’s check that you wrote them down correctly. Please enter words\n**%1$@**, **%2$@** and **%3$@** below:"; +"Wallet.WordCheck.Continue" = "Continue"; +"Wallet.WordCheck.IncorrectHeader" = "Incorrect words!"; +"Wallet.WordCheck.IncorrectText" = "The secret words you have entered do not match the ones in the list."; +"Wallet.WordCheck.TryAgain" = "Try Again"; +"Wallet.WordCheck.ViewWords" = "View Words"; +"Wallet.WordImport.Title" = "24 Secret Words"; +"Wallet.WordImport.Text" = "Please restore access to your wallet by\nentering the 24 secret words you wrote down when creating the wallet."; +"Wallet.WordImport.Continue" = "Continue"; +"Wallet.WordImport.CanNotRemember" = "I don't have them"; +"Wallet.WordImport.IncorrectTitle" = "Incorrect words"; +"Wallet.WordImport.IncorrectText" = "Sorry, you have entered incorrect secret words. Please double check and try again."; +"Wallet.Words.Title" = "24 Secret Words"; +"Wallet.Words.Text" = "Write down these 24 words in the correct order and store them in a secret place.\n\nUse these secret words to restore access to your wallet if you lose your passcode or Telegram account."; +"Wallet.Words.Done" = "Done"; +"Wallet.Words.NotDoneTitle" = "Sure Done?"; +"Wallet.Words.NotDoneText" = "You didn't have enough time to write those words down."; +"Wallet.Words.NotDoneOk" = "OK, Sorry"; +"Wallet.Words.NotDoneResponse" = "Apologies Accepted"; +"Wallet.Send.NetworkErrorTitle" = "No network"; +"Wallet.Send.NetworkErrorText" = "Couldn't send grams. Please make sure your internet connection is working and try again."; +"Wallet.Send.ErrorNotEnoughFundsTitle" = "Insufficient Grams"; +"Wallet.Send.ErrorNotEnoughFundsText" = "Unfortunately, your transfer couldn't be completed. You don't have enough grams."; +"Wallet.Send.ErrorInvalidAddress" = "Invalid wallet address. Please correct and try again."; +"Wallet.Send.ErrorDecryptionFailed" = "Please make sure that your device has a passcode set in iOS Settings and try again."; +"Wallet.Send.UninitializedTitle" = "Warning"; +"Wallet.Send.UninitializedText" = "This address belongs to an empty wallet. Are you sure you want to transfer grams to it?"; +"Wallet.Send.SendAnyway" = "Send Anyway"; +"Wallet.Receive.CreateInvoice" = "Create Invoice"; +"Wallet.Receive.CreateInvoiceInfo" = "You can specify amount and purpose of the payment to save the sender some time."; +"Wallet.CreateInvoice.Title" = "Create Invoice"; +"Wallet.Navigation.Close" = "Close"; +"Wallet.Navigation.Back" = "Back"; +"Wallet.Navigation.Done" = "Done"; +"Wallet.Navigation.Cancel" = "Cancel"; +"Wallet.Alert.OK" = "OK"; +"Wallet.Alert.Cancel" = "Cancel"; +"Wallet.Month.GenJanuary" = "January"; +"Wallet.Month.GenFebruary" = "February"; +"Wallet.Month.GenMarch" = "March"; +"Wallet.Month.GenApril" = "April"; +"Wallet.Month.GenMay" = "May"; +"Wallet.Month.GenJune" = "June"; +"Wallet.Month.GenJuly" = "July"; +"Wallet.Month.GenAugust" = "August"; +"Wallet.Month.GenSeptember" = "September"; +"Wallet.Month.GenOctober" = "October"; +"Wallet.Month.GenNovember" = "November"; +"Wallet.Month.GenDecember" = "December"; +"Wallet.Month.ShortJanuary" = "Jan"; +"Wallet.Month.ShortFebruary" = "Feb"; +"Wallet.Month.ShortMarch" = "Mar"; +"Wallet.Month.ShortApril" = "Apr"; +"Wallet.Month.ShortMay" = "May"; +"Wallet.Month.ShortJune" = "Jun"; +"Wallet.Month.ShortJuly" = "Jul"; +"Wallet.Month.ShortAugust" = "Aug"; +"Wallet.Month.ShortSeptember" = "Sep"; +"Wallet.Month.ShortOctober" = "Oct"; +"Wallet.Month.ShortNovember" = "Nov"; +"Wallet.Month.ShortDecember" = "Dec"; +"Wallet.Weekday.Today" = "Today"; +"Wallet.Weekday.Yesterday" = "Yesterday"; +"Wallet.Info.TransactionDateHeader" = "%1$@ %2$@"; +"Wallet.Info.TransactionDateHeaderYear" = "%1$@ %2$@, %3$@"; +"Wallet.UnknownError" = "An error occurred. Please try again later."; +"Wallet.ContextMenuCopy" = "Copy"; +"Wallet.Time.PreciseDate_m1" = "Jan %1$@, %2$@ at %3$@"; +"Wallet.Time.PreciseDate_m2" = "Feb %1$@, %2$@ at %3$@"; +"Wallet.Time.PreciseDate_m3" = "Mar %1$@, %2$@ at %3$@"; +"Wallet.Time.PreciseDate_m4" = "Apr %1$@, %2$@ at %3$@"; +"Wallet.Time.PreciseDate_m5" = "May %1$@, %2$@ at %3$@"; +"Wallet.Time.PreciseDate_m6" = "Jun %1$@, %2$@ at %3$@"; +"Wallet.Time.PreciseDate_m7" = "Jul %1$@, %2$@ at %3$@"; +"Wallet.Time.PreciseDate_m8" = "Aug %1$@, %2$@ at %3$@"; +"Wallet.Time.PreciseDate_m9" = "Sep %1$@, %2$@ at %3$@"; +"Wallet.Time.PreciseDate_m10" = "Oct %1$@, %2$@ at %3$@"; +"Wallet.Time.PreciseDate_m11" = "Nov %1$@, %2$@ at %3$@"; +"Wallet.Time.PreciseDate_m12" = "Dec %1$@, %2$@ at %3$@"; +"Wallet.VoiceOver.Editing.ClearText" = "Clear text"; diff --git a/Wallet/Strings/es.lproj/Localizable.strings b/Wallet/Strings/es.lproj/Localizable.strings new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Wallet/Strings/fr.lproj/Localizable.strings b/Wallet/Strings/fr.lproj/Localizable.strings new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Wallet/Strings/id.lproj/Localizable.strings b/Wallet/Strings/id.lproj/Localizable.strings new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Wallet/Strings/it.lproj/Localizable.strings b/Wallet/Strings/it.lproj/Localizable.strings new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Wallet/Strings/ko.lproj/Localizable.strings b/Wallet/Strings/ko.lproj/Localizable.strings new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Wallet/Strings/ms.lproj/Localizable.strings b/Wallet/Strings/ms.lproj/Localizable.strings new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Wallet/Strings/nl.lproj/Localizable.strings b/Wallet/Strings/nl.lproj/Localizable.strings new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Wallet/Strings/pt.lproj/Localizable.strings b/Wallet/Strings/pt.lproj/Localizable.strings new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Wallet/Strings/ru.lproj/Localizable.strings b/Wallet/Strings/ru.lproj/Localizable.strings new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Wallet/Strings/tr.lproj/Localizable.strings b/Wallet/Strings/tr.lproj/Localizable.strings new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Wallet/Strings/uk.lproj/Localizable.strings b/Wallet/Strings/uk.lproj/Localizable.strings new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Wallet/Wallet.entitlements b/Wallet/Wallet.entitlements new file mode 100644 index 0000000000..6631ffa6f2 --- /dev/null +++ b/Wallet/Wallet.entitlements @@ -0,0 +1,6 @@ + + + + + + diff --git a/submodules/AccountContext/BUCK b/submodules/AccountContext/BUCK index 255438a28a..896ec1bb9d 100644 --- a/submodules/AccountContext/BUCK +++ b/submodules/AccountContext/BUCK @@ -15,6 +15,7 @@ static_library( "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit#dynamic", "//submodules/Postbox:Postbox#dynamic", "//submodules/TelegramCore:TelegramCore#dynamic", + "//submodules/WalletCore:WalletCore", ], frameworks = [ "$SDKROOT/System/Library/Frameworks/Foundation.framework", diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index 03a75f467e..0540efdaa0 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -7,6 +7,7 @@ import SwiftSignalKit import Display import DeviceLocationManager import TemporaryCachedPeerDataManager +import WalletCore public final class TelegramApplicationOpenUrlCompletion { public let completion: (Bool) -> Void @@ -457,6 +458,26 @@ private final class TonInstanceData { var instance: TonInstance? } +private final class TonNetworkProxyImpl: TonNetworkProxy { + private let network: Network + + init(network: Network) { + self.network = network + } + + func request(data: Data, timeout timeoutValue: Double, completion: @escaping (TonNetworkProxyResult) -> Void) -> Disposable { + return (walletProxyRequest(network: self.network, data: data) + |> timeout(timeoutValue, queue: .concurrentDefaultQueue(), alternate: .fail(.generic(500, "Local Timeout")))).start(next: { data in + completion(.reponse(data)) + }, error: { error in + switch error { + case let .generic(_, text): + completion(.error(text)) + } + }) + } +} + public final class StoredTonContext { private let basePath: String private let postbox: Postbox @@ -477,7 +498,7 @@ public final class StoredTonContext { return TonContext(instance: instance, keychain: self.keychain) } else { data.config = config - let instance = TonInstance(basePath: self.basePath, config: config, blockchainName: blockchainName, network: enableProxy ? self.network : nil) + let instance = TonInstance(basePath: self.basePath, config: config, blockchainName: blockchainName, proxy: enableProxy ? TonNetworkProxyImpl(network: self.network) : nil) data.instance = instance return TonContext(instance: instance, keychain: self.keychain) } @@ -506,6 +527,7 @@ public protocol AccountContext: class { var peerChannelMemberCategoriesContextsManager: PeerChannelMemberCategoriesContextsManager { get } var wallpaperUploadManager: WallpaperUploadManager? { get } var watchManager: WatchManager? { get } + var hasWallets: Signal { get } var currentLimitsConfiguration: Atomic { get } diff --git a/submodules/ActivityIndicator/BUCK b/submodules/ActivityIndicator/BUCK index aa2ce7825c..fd29559549 100644 --- a/submodules/ActivityIndicator/BUCK +++ b/submodules/ActivityIndicator/BUCK @@ -7,7 +7,7 @@ static_library( ]), deps = [ "//submodules/AsyncDisplayKit:AsyncDisplayKit#shared", - "//submodules/TelegramPresentationData:TelegramPresentationData", + "//submodules/Display:Display#shared", ], frameworks = [ "$SDKROOT/System/Library/Frameworks/Foundation.framework", diff --git a/submodules/ActivityIndicator/Sources/ActivityIndicator.swift b/submodules/ActivityIndicator/Sources/ActivityIndicator.swift index ef3162a27f..205ef1c454 100644 --- a/submodules/ActivityIndicator/Sources/ActivityIndicator.swift +++ b/submodules/ActivityIndicator/Sources/ActivityIndicator.swift @@ -1,7 +1,19 @@ import Foundation import UIKit import AsyncDisplayKit -import TelegramPresentationData +import Display + +private func generateIndefiniteActivityIndicatorImage(color: UIColor, diameter: CGFloat = 22.0, lineWidth: CGFloat = 2.0) -> UIImage? { + return generateImage(CGSize(width: diameter, height: diameter), rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + context.setStrokeColor(color.cgColor) + context.setLineWidth(lineWidth) + context.setLineCap(.round) + let cutoutAngle: CGFloat = CGFloat.pi * 30.0 / 180.0 + context.addArc(center: CGPoint(x: size.width / 2.0, y: size.height / 2.0), radius: size.width / 2.0 - lineWidth / 2.0, startAngle: 0.0, endAngle: CGFloat.pi * 2.0 - cutoutAngle, clockwise: false) + context.strokePath() + }) +} private func convertIndicatorColor(_ color: UIColor) -> UIColor { if color.isEqual(UIColor(rgb: 0x007ee5)) { @@ -16,13 +28,13 @@ private func convertIndicatorColor(_ color: UIColor) -> UIColor { } public enum ActivityIndicatorType: Equatable { - case navigationAccent(PresentationTheme) + case navigationAccent(UIColor) case custom(UIColor, CGFloat, CGFloat, Bool) public static func ==(lhs: ActivityIndicatorType, rhs: ActivityIndicatorType) -> Bool { switch lhs { - case let .navigationAccent(lhsTheme): - if case let .navigationAccent(rhsTheme) = rhs, lhsTheme === rhsTheme { + case let .navigationAccent(lhsColor): + if case let .navigationAccent(rhsColor) = rhs, lhsColor.isEqual(rhsColor) { return true } else { return false @@ -46,15 +58,15 @@ public final class ActivityIndicator: ASDisplayNode { public var type: ActivityIndicatorType { didSet { switch self.type { - case let .navigationAccent(theme): - self.indicatorNode.image = PresentationResourcesRootController.navigationIndefiniteActivityImage(theme) + case let .navigationAccent(color): + self.indicatorNode.image = generateIndefiniteActivityIndicatorImage(color: color) case let .custom(color, diameter, lineWidth, _): self.indicatorNode.image = generateIndefiniteActivityIndicatorImage(color: color, diameter: diameter, lineWidth: lineWidth) } switch self.type { - case let .navigationAccent(theme): - self.indicatorView?.color = theme.rootController.navigationBar.controlColor + case let .navigationAccent(color): + self.indicatorView?.color = color case let .custom(color, _, _, _): self.indicatorView?.color = convertIndicatorColor(color) } @@ -90,8 +102,8 @@ public final class ActivityIndicator: ASDisplayNode { } switch type { - case let .navigationAccent(theme): - self.indicatorNode.image = PresentationResourcesRootController.navigationIndefiniteActivityImage(theme) + case let .navigationAccent(color): + self.indicatorNode.image = generateIndefiniteActivityIndicatorImage(color: color) case let .custom(color, diameter, lineWidth, forceCustom): self.indicatorNode.image = generateIndefiniteActivityIndicatorImage(color: color, diameter: diameter, lineWidth: lineWidth) if forceCustom { @@ -105,8 +117,8 @@ public final class ActivityIndicator: ASDisplayNode { let indicatorView = UIActivityIndicatorView(style: .whiteLarge) switch self.type { - case let .navigationAccent(theme): - indicatorView.color = theme.rootController.navigationBar.controlColor + case let .navigationAccent(color): + indicatorView.color = color case let .custom(color, _, _, forceCustom): indicatorView.color = convertIndicatorColor(color) if !forceCustom { diff --git a/submodules/AnimatedStickerNode/BUCK b/submodules/AnimatedStickerNode/BUCK new file mode 100644 index 0000000000..bdc95d5051 --- /dev/null +++ b/submodules/AnimatedStickerNode/BUCK @@ -0,0 +1,20 @@ +load("//Config:buck_rule_macros.bzl", "static_library") + +static_library( + name = "AnimatedStickerNode", + srcs = glob([ + "Sources/**/*.swift", + ]), + deps = [ + "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit#shared", + "//submodules/AsyncDisplayKit:AsyncDisplayKit#shared", + "//submodules/Display:Display#shared", + "//submodules/YuvConversion:YuvConversion", + "//submodules/GZip:GZip", + "//submodules/rlottie:RLottieBinding", + ], + frameworks = [ + "$SDKROOT/System/Library/Frameworks/Foundation.framework", + "$SDKROOT/System/Library/Frameworks/UIKit.framework", + ], +) diff --git a/submodules/AnimationUI/Sources/AnimatedStickerNode.swift b/submodules/AnimatedStickerNode/Sources/AnimatedStickerNode.swift similarity index 89% rename from submodules/AnimationUI/Sources/AnimatedStickerNode.swift rename to submodules/AnimatedStickerNode/Sources/AnimatedStickerNode.swift index d6c0259194..92df089561 100644 --- a/submodules/AnimationUI/Sources/AnimatedStickerNode.swift +++ b/submodules/AnimatedStickerNode/Sources/AnimatedStickerNode.swift @@ -1,15 +1,10 @@ import Foundation import SwiftSignalKit -import Postbox -import TelegramCore import Compression import Display import AsyncDisplayKit import RLottieBinding import GZip -import Tuples -import MediaResources -import StickerResources private let sharedQueue = Queue() @@ -301,14 +296,29 @@ public struct AnimatedStickerStatus: Equatable { } } -public enum AnimatedStickerNodeResource { - case resource(Account, MediaResource) - case localFile(String) +public protocol AnimatedStickerNodeSource { + func cachedDataPath(width: Int, height: Int) -> Signal + func directDataPath() -> Signal +} + +public final class AnimatedStickerNodeLocalFileSource: AnimatedStickerNodeSource { + public let path: String + + public init(path: String) { + self.path = path + } + + public func directDataPath() -> Signal { + return .single(self.path) + } + + public func cachedDataPath(width: Int, height: Int) -> Signal { + return .never() + } } public final class AnimatedStickerNode: ASDisplayNode { private let queue: Queue - private var fileReference: FileMediaReference? private let disposable = MetaDisposable() private let fetchDisposable = MetaDisposable() private let eventsNode: AnimatedStickerNodeDisplayEvents @@ -321,7 +331,7 @@ public final class AnimatedStickerNode: ASDisplayNode { private let timer = Atomic(value: nil) - private var directData: Tuple4? + private var directData: (Data, String, Int, Int)? private var cachedData: Data? private var renderer: (AnimationRenderer & ASDisplayNode)? @@ -385,19 +395,19 @@ public final class AnimatedStickerNode: ASDisplayNode { self.addSubnode(self.renderer!) } - public func setup(resource: AnimatedStickerNodeResource, fitzModifier: EmojiFitzModifier? = nil, width: Int, height: Int, playbackMode: AnimatedStickerPlaybackMode = .loop, mode: AnimatedStickerMode) { + public func setup(source: AnimatedStickerNodeSource, width: Int, height: Int, playbackMode: AnimatedStickerPlaybackMode = .loop, mode: AnimatedStickerMode) { if width < 2 || height < 2 { return } self.playbackMode = playbackMode switch mode { case .direct: - let f: (MediaResourceData) -> Void = { [weak self] data in - guard let strongSelf = self, data.complete else { + let f: (String) -> Void = { [weak self] path in + guard let strongSelf = self else { return } - if let directData = try? Data(contentsOf: URL(fileURLWithPath: data.path), options: [.mappedRead]) { - strongSelf.directData = Tuple(directData, data.path, width, height) + if let directData = try? Data(contentsOf: URL(fileURLWithPath: path), options: [.mappedRead]) { + strongSelf.directData = (directData, path, width, height) } if strongSelf.isPlaying { strongSelf.play() @@ -405,32 +415,23 @@ public final class AnimatedStickerNode: ASDisplayNode { strongSelf.play(firstFrame: true) } } - switch resource { - case let .resource(account, resource): - self.disposable.set((account.postbox.mediaBox.resourceData(resource) - |> deliverOnMainQueue).start(next: { data in - f(data) - })) - case let .localFile(path): - f(MediaResourceData(path: path, offset: 0, size: Int(Int32.max - 1), complete: true)) - } + self.disposable.set((source.directDataPath() + |> deliverOnMainQueue).start(next: { path in + f(path) + })) case .cached: - switch resource { - case let .resource(account, resource): - self.disposable.set((chatMessageAnimationData(postbox: account.postbox, resource: resource, fitzModifier: fitzModifier, width: width, height: height, synchronousLoad: false) - |> deliverOnMainQueue).start(next: { [weak self] data in - if let strongSelf = self, data.complete { - strongSelf.cachedData = try? Data(contentsOf: URL(fileURLWithPath: data.path), options: [.mappedRead]) - if strongSelf.isPlaying { - strongSelf.play() - } else if strongSelf.canDisplayFirstFrame { - strongSelf.play(firstFrame: true) - } - } - })) - case .localFile: - break - } + self.disposable.set((source.cachedDataPath(width: width, height: height) + |> deliverOnMainQueue).start(next: { [weak self] path in + guard let strongSelf = self else { + return + } + strongSelf.cachedData = try? Data(contentsOf: URL(fileURLWithPath: path), options: [.mappedRead]) + if strongSelf.isPlaying { + strongSelf.play() + } else if strongSelf.canDisplayFirstFrame { + strongSelf.play(firstFrame: true) + } + })) } } @@ -466,7 +467,7 @@ public final class AnimatedStickerNode: ASDisplayNode { self.queue.async { [weak self] in var maybeFrameSource: AnimatedStickerFrameSource? if let directData = directData { - maybeFrameSource = AnimatedStickerDirectFrameSource(queue: queue, data: directData._0, width: directData._2, height: directData._3) + maybeFrameSource = AnimatedStickerDirectFrameSource(queue: queue, data: directData.0, width: directData.2, height: directData.3) } else if let cachedData = cachedData { if #available(iOS 9.0, *) { maybeFrameSource = AnimatedStickerCachedFrameSource(queue: queue, data: cachedData) @@ -539,7 +540,7 @@ public final class AnimatedStickerNode: ASDisplayNode { self.queue.async { [weak self] in var maybeFrameSource: AnimatedStickerFrameSource? if let directData = directData { - maybeFrameSource = AnimatedStickerDirectFrameSource(queue: queue, data: directData._0, width: directData._2, height: directData._3) + maybeFrameSource = AnimatedStickerDirectFrameSource(queue: queue, data: directData.0, width: directData.2, height: directData.3) } else if let cachedData = cachedData { if #available(iOS 9.0, *) { maybeFrameSource = AnimatedStickerCachedFrameSource(queue: queue, data: cachedData) diff --git a/submodules/AnimationUI/Sources/AnimationRenderer.swift b/submodules/AnimatedStickerNode/Sources/AnimationRenderer.swift similarity index 100% rename from submodules/AnimationUI/Sources/AnimationRenderer.swift rename to submodules/AnimatedStickerNode/Sources/AnimationRenderer.swift diff --git a/submodules/AnimationUI/Sources/SoftwareAnimationRenderer.swift b/submodules/AnimatedStickerNode/Sources/SoftwareAnimationRenderer.swift similarity index 100% rename from submodules/AnimationUI/Sources/SoftwareAnimationRenderer.swift rename to submodules/AnimatedStickerNode/Sources/SoftwareAnimationRenderer.swift diff --git a/submodules/AnimationUI/Sources/AnimationUI.h b/submodules/AnimationUI/Sources/AnimationUI.h deleted file mode 100644 index bcb24521bd..0000000000 --- a/submodules/AnimationUI/Sources/AnimationUI.h +++ /dev/null @@ -1,19 +0,0 @@ -// -// AnimationUI.h -// AnimationUI -// -// Created by Peter on 8/1/19. -// Copyright © 2019 Telegram Messenger LLP. All rights reserved. -// - -#import - -//! Project version number for AnimationUI. -FOUNDATION_EXPORT double AnimationUIVersionNumber; - -//! Project version string for AnimationUI. -FOUNDATION_EXPORT const unsigned char AnimationUIVersionString[]; - -// In this header, you should import all the public headers of your framework using statements like #import - - diff --git a/submodules/AuthorizationUI/BUCK b/submodules/AuthorizationUI/BUCK index b0cf635904..435baaaa24 100644 --- a/submodules/AuthorizationUI/BUCK +++ b/submodules/AuthorizationUI/BUCK @@ -10,6 +10,7 @@ static_library( "//submodules/TelegramCore:TelegramCore#shared", "//submodules/Display:Display#shared", "//submodules/TextFormat:TextFormat", + "//submodules/Markdown:Markdown", "//submodules/TelegramPresentationData:TelegramPresentationData", ], frameworks = [ diff --git a/submodules/AuthorizationUI/Sources/AuthorizationOptionText.swift b/submodules/AuthorizationUI/Sources/AuthorizationOptionText.swift index aea3869ed0..9cc005a6f5 100644 --- a/submodules/AuthorizationUI/Sources/AuthorizationOptionText.swift +++ b/submodules/AuthorizationUI/Sources/AuthorizationOptionText.swift @@ -3,6 +3,7 @@ import TelegramCore import Display import TelegramPresentationData import TextFormat +import Markdown public func authorizationCurrentOptionText(_ type: SentAuthorizationCodeType, strings: PresentationStrings, primaryColor: UIColor, accentColor: UIColor) -> NSAttributedString { switch type { diff --git a/submodules/BotPaymentsUI/BUCK b/submodules/BotPaymentsUI/BUCK index 6b633815f2..caf603a34b 100644 --- a/submodules/BotPaymentsUI/BUCK +++ b/submodules/BotPaymentsUI/BUCK @@ -19,7 +19,7 @@ static_library( "//submodules/Stripe:Stripe", "//submodules/CountrySelectionUI:CountrySelectionUI", "//submodules/AppBundle:AppBundle", - "//submodules/PresentationDataUtils:PresentationDataUtils", + "//submodules/PresentationDataUtils:PresentationDataUtils", ], frameworks = [ "$SDKROOT/System/Library/Frameworks/Foundation.framework", diff --git a/submodules/BotPaymentsUI/Sources/BotCheckoutInfoController.swift b/submodules/BotPaymentsUI/Sources/BotCheckoutInfoController.swift index b959d65554..ac4c8706e4 100644 --- a/submodules/BotPaymentsUI/Sources/BotCheckoutInfoController.swift +++ b/submodules/BotPaymentsUI/Sources/BotCheckoutInfoController.swift @@ -99,7 +99,7 @@ final class BotCheckoutInfoController: ViewController { switch status { case .verifying: if strongSelf.activityItem == nil { - strongSelf.activityItem = UIBarButtonItem(customDisplayNode: ProgressNavigationButtonNode(theme: strongSelf.presentationData.theme)) + strongSelf.activityItem = UIBarButtonItem(customDisplayNode: ProgressNavigationButtonNode(color: strongSelf.presentationData.theme.rootController.navigationBar.controlColor)) strongSelf.navigationItem.setRightBarButton(strongSelf.activityItem, animated: false) } default: diff --git a/submodules/BotPaymentsUI/Sources/BotCheckoutNativeCardEntryController.swift b/submodules/BotPaymentsUI/Sources/BotCheckoutNativeCardEntryController.swift index dcefe1f5d2..e49e48a409 100644 --- a/submodules/BotPaymentsUI/Sources/BotCheckoutNativeCardEntryController.swift +++ b/submodules/BotPaymentsUI/Sources/BotCheckoutNativeCardEntryController.swift @@ -97,7 +97,7 @@ final class BotCheckoutNativeCardEntryController: ViewController { switch status { case .verifying: if strongSelf.activityItem == nil { - strongSelf.activityItem = UIBarButtonItem(customDisplayNode: ProgressNavigationButtonNode(theme: strongSelf.presentationData.theme)) + strongSelf.activityItem = UIBarButtonItem(customDisplayNode: ProgressNavigationButtonNode(color: strongSelf.presentationData.theme.rootController.navigationBar.controlColor)) strongSelf.navigationItem.setRightBarButton(strongSelf.activityItem, animated: false) } default: diff --git a/submodules/ContactsPeerItem/BUCK b/submodules/ContactsPeerItem/BUCK index 52f3dd428a..145b5971c7 100644 --- a/submodules/ContactsPeerItem/BUCK +++ b/submodules/ContactsPeerItem/BUCK @@ -22,6 +22,7 @@ static_library( "//submodules/TelegramUIPreferences:TelegramUIPreferences", "//submodules/ListSectionHeaderNode:ListSectionHeaderNode", "//submodules/ContextUI:ContextUI", + "//submodules/PresentationDataUtils:PresentationDataUtils", ], frameworks = [ "$SDKROOT/System/Library/Frameworks/Foundation.framework", diff --git a/submodules/ItemListAddressItem/BUCK b/submodules/ItemListAddressItem/BUCK index f999b68ec5..ec8a651a03 100644 --- a/submodules/ItemListAddressItem/BUCK +++ b/submodules/ItemListAddressItem/BUCK @@ -14,6 +14,7 @@ static_library( "//submodules/AccountContext:AccountContext", "//submodules/TextFormat:TextFormat", "//submodules/AppBundle:AppBundle", + "//submodules/PresentationDataUtils:PresentationDataUtils", ], frameworks = [ "$SDKROOT/System/Library/Frameworks/Foundation.framework", diff --git a/submodules/ItemListAvatarAndNameInfoItem/BUCK b/submodules/ItemListAvatarAndNameInfoItem/BUCK index 76b3319fce..314681d12c 100644 --- a/submodules/ItemListAvatarAndNameInfoItem/BUCK +++ b/submodules/ItemListAvatarAndNameInfoItem/BUCK @@ -18,6 +18,7 @@ static_library( "//submodules/ActivityIndicator:ActivityIndicator", "//submodules/ItemListUI:ItemListUI", "//submodules/AppBundle:AppBundle", + "//submodules/PresentationDataUtils:PresentationDataUtils", ], frameworks = [ "$SDKROOT/System/Library/Frameworks/Foundation.framework", diff --git a/submodules/ItemListPeerActionItem/BUCK b/submodules/ItemListPeerActionItem/BUCK index ad1ce4cefe..604dc5c10a 100644 --- a/submodules/ItemListPeerActionItem/BUCK +++ b/submodules/ItemListPeerActionItem/BUCK @@ -11,6 +11,7 @@ static_library( "//submodules/Display:Display#shared", "//submodules/TelegramPresentationData:TelegramPresentationData", "//submodules/ItemListUI:ItemListUI", + "//submodules/PresentationDataUtils:PresentationDataUtils", ], frameworks = [ "$SDKROOT/System/Library/Frameworks/Foundation.framework", diff --git a/submodules/ItemListPeerItem/BUCK b/submodules/ItemListPeerItem/BUCK index d634f0c766..465b5ecae0 100644 --- a/submodules/ItemListPeerItem/BUCK +++ b/submodules/ItemListPeerItem/BUCK @@ -18,6 +18,7 @@ static_library( "//submodules/ItemListUI:ItemListUI", "//submodules/TelegramUIPreferences:TelegramUIPreferences", "//submodules/ContextUI:ContextUI", + "//submodules/PresentationDataUtils:PresentationDataUtils", ], frameworks = [ "$SDKROOT/System/Library/Frameworks/Foundation.framework", diff --git a/submodules/ItemListStickerPackItem/BUCK b/submodules/ItemListStickerPackItem/BUCK index 3da5704d8b..c2b14e9cef 100644 --- a/submodules/ItemListStickerPackItem/BUCK +++ b/submodules/ItemListStickerPackItem/BUCK @@ -14,7 +14,9 @@ static_library( "//submodules/TelegramPresentationData:TelegramPresentationData", "//submodules/ItemListUI:ItemListUI", "//submodules/StickerResources:StickerResources", - "//submodules/AnimationUI:AnimationUI", + "//submodules/AnimatedStickerNode:AnimatedStickerNode", + "//submodules/TelegramAnimatedStickerNode:TelegramAnimatedStickerNode", + "//submodules/PresentationDataUtils:PresentationDataUtils", ], frameworks = [ "$SDKROOT/System/Library/Frameworks/Foundation.framework", diff --git a/submodules/ItemListStickerPackItem/Sources/ItemListStickerPackItem.swift b/submodules/ItemListStickerPackItem/Sources/ItemListStickerPackItem.swift index 2c8664528f..f726e85c0c 100644 --- a/submodules/ItemListStickerPackItem/Sources/ItemListStickerPackItem.swift +++ b/submodules/ItemListStickerPackItem/Sources/ItemListStickerPackItem.swift @@ -9,7 +9,8 @@ import TelegramPresentationData import ItemListUI import PresentationDataUtils import StickerResources -import AnimationUI +import AnimatedStickerNode +import TelegramAnimatedStickerNode public struct ItemListStickerPackItemEditing: Equatable { public var editable: Bool @@ -600,7 +601,7 @@ class ItemListStickerPackItemNode: ItemListRevealOptionsItemNode { animationNode = AnimatedStickerNode() strongSelf.animationNode = animationNode strongSelf.addSubnode(animationNode) - animationNode.setup(resource: .resource(item.account, resource), width: 80, height: 80, mode: .cached) + animationNode.setup(source: AnimatedStickerResourceSource(account: item.account, resource: resource), width: 80, height: 80, mode: .cached) } animationNode.visibility = strongSelf.visibility != .none && item.playAnimatedStickers animationNode.isHidden = !item.playAnimatedStickers diff --git a/submodules/ItemListUI/BUCK b/submodules/ItemListUI/BUCK index 47ab22d750..51163a8812 100644 --- a/submodules/ItemListUI/BUCK +++ b/submodules/ItemListUI/BUCK @@ -12,11 +12,14 @@ static_library( "//submodules/TelegramPresentationData:TelegramPresentationData", "//submodules/MergeLists:MergeLists", "//submodules/TextFormat:TextFormat", + "//submodules/Markdown:Markdown", "//submodules/ProgressNavigationButtonNode:ProgressNavigationButtonNode", "//submodules/SwitchNode:SwitchNode", - "//submodules/AnimationUI:AnimationUI", + "//submodules/AnimatedStickerNode:AnimatedStickerNode", + "//submodules/TelegramAnimatedStickerNode:TelegramAnimatedStickerNode", "//submodules/CheckNode:CheckNode", "//submodules/SegmentedControlNode:SegmentedControlNode", + "//submodules/AccountContext:AccountContext", ], frameworks = [ "$SDKROOT/System/Library/Frameworks/Foundation.framework", diff --git a/submodules/ItemListUI/Sources/ItemListController.swift b/submodules/ItemListUI/Sources/ItemListController.swift index c0ceb9b119..e59a178ae5 100644 --- a/submodules/ItemListUI/Sources/ItemListController.swift +++ b/submodules/ItemListUI/Sources/ItemListController.swift @@ -350,7 +350,7 @@ open class ItemListController: ViewController, KeyShortcutResponder, Presentable for (content, style, _) in rightNavigationButtonTitleAndStyle { let item: UIBarButtonItem if case .activity = style { - item = UIBarButtonItem(customDisplayNode: ProgressNavigationButtonNode(theme: controllerState.theme)) + item = UIBarButtonItem(customDisplayNode: ProgressNavigationButtonNode(color: controllerState.theme.rootController.navigationBar.controlColor)) } else { let action: Selector = (index == 0 && rightNavigationButtonTitleAndStyle.count > 1) ? #selector(strongSelf.secondaryRightNavigationButtonPressed) : #selector(strongSelf.rightNavigationButtonPressed) switch content { @@ -407,7 +407,7 @@ open class ItemListController: ViewController, KeyShortcutResponder, Presentable var items = strongSelf.navigationItem.rightBarButtonItems ?? [] for i in 0 ..< strongSelf.rightNavigationButtonTitleAndStyle.count { if case .activity = strongSelf.rightNavigationButtonTitleAndStyle[i].1 { - items[i] = UIBarButtonItem(customDisplayNode: ProgressNavigationButtonNode(theme: controllerState.theme))! + items[i] = UIBarButtonItem(customDisplayNode: ProgressNavigationButtonNode(color: controllerState.theme.rootController.navigationBar.controlColor))! } } strongSelf.navigationItem.setRightBarButtonItems(items, animated: false) diff --git a/submodules/ItemListUI/Sources/Items/ItemListInfoItem.swift b/submodules/ItemListUI/Sources/Items/ItemListInfoItem.swift index fa17f13671..0cc2c753b4 100644 --- a/submodules/ItemListUI/Sources/Items/ItemListInfoItem.swift +++ b/submodules/ItemListUI/Sources/Items/ItemListInfoItem.swift @@ -5,6 +5,7 @@ import AsyncDisplayKit import SwiftSignalKit import TelegramPresentationData import TextFormat +import Markdown public enum InfoListItemText { case plain(String) diff --git a/submodules/ItemListUI/Sources/Items/ItemListTextItem.swift b/submodules/ItemListUI/Sources/Items/ItemListTextItem.swift index 5840ca926c..6f67e65615 100644 --- a/submodules/ItemListUI/Sources/Items/ItemListTextItem.swift +++ b/submodules/ItemListUI/Sources/Items/ItemListTextItem.swift @@ -5,6 +5,7 @@ import AsyncDisplayKit import SwiftSignalKit import TelegramPresentationData import TextFormat +import Markdown public enum ItemListTextItemText { case plain(String) diff --git a/submodules/LanguageLinkPreviewUI/BUCK b/submodules/LanguageLinkPreviewUI/BUCK index 705b6bac09..ccc998474e 100644 --- a/submodules/LanguageLinkPreviewUI/BUCK +++ b/submodules/LanguageLinkPreviewUI/BUCK @@ -14,6 +14,7 @@ static_library( "//submodules/TelegramPresentationData:TelegramPresentationData", "//submodules/AccountContext:AccountContext", "//submodules/TextFormat:TextFormat", + "//submodules/Markdown:Markdown", "//submodules/ShareController:ShareController", "//submodules/AlertUI:AlertUI", "//submodules/PresentationDataUtils:PresentationDataUtils", diff --git a/submodules/LanguageLinkPreviewUI/Sources/LanguageLinkPreviewContentNode.swift b/submodules/LanguageLinkPreviewUI/Sources/LanguageLinkPreviewContentNode.swift index d7dd9e0a88..ca3377b5a7 100644 --- a/submodules/LanguageLinkPreviewUI/Sources/LanguageLinkPreviewContentNode.swift +++ b/submodules/LanguageLinkPreviewUI/Sources/LanguageLinkPreviewContentNode.swift @@ -8,6 +8,7 @@ import TelegramPresentationData import TextFormat import AccountContext import ShareController +import Markdown final class LanguageLinkPreviewContentNode: ASDisplayNode, ShareContentContainerNode { private var contentOffsetUpdated: ((CGFloat, ContainedViewLayoutTransition) -> Void)? diff --git a/submodules/Markdown/BUCK b/submodules/Markdown/BUCK new file mode 100644 index 0000000000..a7e2e42fc3 --- /dev/null +++ b/submodules/Markdown/BUCK @@ -0,0 +1,14 @@ +load("//Config:buck_rule_macros.bzl", "static_library") + +static_library( + name = "Markdown", + srcs = glob([ + "Sources/**/*.swift", + ]), + deps = [ + ], + frameworks = [ + "$SDKROOT/System/Library/Frameworks/Foundation.framework", + "$SDKROOT/System/Library/Frameworks/UIKit.framework", + ], +) diff --git a/submodules/TextFormat/Sources/Markdown.swift b/submodules/Markdown/Sources/Markdown.swift similarity index 99% rename from submodules/TextFormat/Sources/Markdown.swift rename to submodules/Markdown/Sources/Markdown.swift index 1857305339..fbb4108e0d 100644 --- a/submodules/TextFormat/Sources/Markdown.swift +++ b/submodules/Markdown/Sources/Markdown.swift @@ -1,6 +1,5 @@ import Foundation import UIKit -import Display private let controlStartCharactersSet = CharacterSet(charactersIn: "[*") private let controlCharactersSet = CharacterSet(charactersIn: "[]()*_-\\") diff --git a/submodules/NotificationSoundSelectionUI/BUCK b/submodules/NotificationSoundSelectionUI/BUCK index 8f391af75a..89ed604125 100644 --- a/submodules/NotificationSoundSelectionUI/BUCK +++ b/submodules/NotificationSoundSelectionUI/BUCK @@ -14,6 +14,7 @@ static_library( "//submodules/ItemListUI:ItemListUI", "//submodules/AccountContext:AccountContext", "//submodules/TelegramStringFormatting:TelegramStringFormatting", + "//submodules/PresentationDataUtils:PresentationDataUtils", ], frameworks = [ "$SDKROOT/System/Library/Frameworks/Foundation.framework", diff --git a/submodules/OverlayStatusController/BUCK b/submodules/OverlayStatusController/BUCK index d177a806b0..a148ac0dd5 100644 --- a/submodules/OverlayStatusController/BUCK +++ b/submodules/OverlayStatusController/BUCK @@ -1,13 +1,27 @@ load("//Config:buck_rule_macros.bzl", "static_library") +apple_resource( + name = "OverlayStatusControllerResources", + files = glob([ + "Resources/**/*", + ], exclude = ["Resources/**/.*"]), + visibility = ["PUBLIC"], +) + static_library( name = "OverlayStatusController", srcs = glob([ "Sources/**/*.swift", + "Sources/**/*.m", + ]), + headers = glob([ + "Sources/**/*.h", + ]), + exported_headers = glob([ + "Sources/**/*.h", ]), deps = [ "//submodules/Display:Display#shared", - "//submodules/LegacyComponents:LegacyComponents", ], frameworks = [ "$SDKROOT/System/Library/Frameworks/Foundation.framework", diff --git a/submodules/OverlayStatusController/Resources/Star@2x.png b/submodules/OverlayStatusController/Resources/Star@2x.png new file mode 100644 index 0000000000..0cbe785016 Binary files /dev/null and b/submodules/OverlayStatusController/Resources/Star@2x.png differ diff --git a/submodules/OverlayStatusController/Resources/Star@3x.png b/submodules/OverlayStatusController/Resources/Star@3x.png new file mode 100644 index 0000000000..5da86261c6 Binary files /dev/null and b/submodules/OverlayStatusController/Resources/Star@3x.png differ diff --git a/submodules/OverlayStatusController/Sources/OverlayStatusController.h b/submodules/OverlayStatusController/Sources/OverlayStatusController.h deleted file mode 100644 index 6662916b18..0000000000 --- a/submodules/OverlayStatusController/Sources/OverlayStatusController.h +++ /dev/null @@ -1,19 +0,0 @@ -// -// OverlayStatusController.h -// OverlayStatusController -// -// Created by Peter on 8/4/19. -// Copyright © 2019 Telegram Messenger LLP. All rights reserved. -// - -#import - -//! Project version number for OverlayStatusController. -FOUNDATION_EXPORT double OverlayStatusControllerVersionNumber; - -//! Project version string for OverlayStatusController. -FOUNDATION_EXPORT const unsigned char OverlayStatusControllerVersionString[]; - -// In this header, you should import all the public headers of your framework using statements like #import - - diff --git a/submodules/OverlayStatusController/Sources/OverlayStatusController.swift b/submodules/OverlayStatusController/Sources/OverlayStatusController.swift index 0a730a7bcd..82fd76fe5b 100644 --- a/submodules/OverlayStatusController/Sources/OverlayStatusController.swift +++ b/submodules/OverlayStatusController/Sources/OverlayStatusController.swift @@ -1,7 +1,7 @@ import Foundation import UIKit import Display -import LegacyComponents +import AppBundle public enum OverlayStatusControllerType { case loading(cancelled: (() -> Void)?) @@ -12,11 +12,11 @@ public enum OverlayStatusControllerType { } private enum OverlayStatusContentController { - case loading(TGProgressWindowController) - case progress(TGProgressWindowController) - case shieldSuccess(TGProxyWindowController, Bool) - case genericSuccess(TGProxyWindowController, Bool) - case starSuccess(TGProxyWindowController) + case loading(ProgressWindowController) + case progress(ProgressWindowController) + case shieldSuccess(ProxyWindowController, Bool) + case genericSuccess(ProxyWindowController, Bool) + case starSuccess(ProxyWindowController) var view: UIView { switch self { @@ -84,21 +84,21 @@ private final class OverlayStatusControllerNode: ViewControllerTracingNode { var isUserInteractionEnabled = true switch type { case let .loading(cancelled): - let controller = TGProgressWindowController(light: style == .light)! + let controller = ProgressWindowController(light: style == .light)! controller.cancelled = { cancelled?() } self.contentController = .loading(controller) case .success: - self.contentController = .progress(TGProgressWindowController(light: style == .light)) + self.contentController = .progress(ProgressWindowController(light: style == .light)) case let .shieldSuccess(text, increasedDelay): - self.contentController = .shieldSuccess(TGProxyWindowController(light: style == .light, text: text, shield: true, star: false), increasedDelay) + self.contentController = .shieldSuccess(ProxyWindowController(light: style == .light, text: text, icon: ProxyWindowController.generateShieldImage(style == .light), isShield: true), increasedDelay) case let .genericSuccess(text, increasedDelay): - let controller = TGProxyWindowController(light: style == .light, text: text, shield: false, star: false)! + let controller = ProxyWindowController(light: style == .light, text: text, icon: nil, isShield: false)! self.contentController = .genericSuccess(controller, increasedDelay) isUserInteractionEnabled = false case let .starSuccess(text): - self.contentController = .genericSuccess(TGProxyWindowController(light: style == .light, text: text, shield: false, star: true), false) + self.contentController = .genericSuccess(ProxyWindowController(light: style == .light, text: text, icon: UIImage(bundleImageName: "Star"), isShield: false), false) } super.init() diff --git a/submodules/OverlayStatusController/Sources/ProgressSpinnerView.h b/submodules/OverlayStatusController/Sources/ProgressSpinnerView.h new file mode 100644 index 0000000000..e1682c02a2 --- /dev/null +++ b/submodules/OverlayStatusController/Sources/ProgressSpinnerView.h @@ -0,0 +1,12 @@ +#import + +@interface ProgressSpinnerView : UIView + +@property (nonatomic, copy) void (^onSuccess)(void); + +- (instancetype)initWithFrame:(CGRect)frame light:(bool)light; + +- (void)setProgress; +- (void)setSucceed; + +@end diff --git a/submodules/OverlayStatusController/Sources/ProgressSpinnerView.m b/submodules/OverlayStatusController/Sources/ProgressSpinnerView.m new file mode 100644 index 0000000000..402f24f9e6 --- /dev/null +++ b/submodules/OverlayStatusController/Sources/ProgressSpinnerView.m @@ -0,0 +1,305 @@ +#import "ProgressSpinnerView.h" + +#define UIColorRGB(rgb) ([[UIColor alloc] initWithRed:(((rgb >> 16) & 0xff) / 255.0f) green:(((rgb >> 8) & 0xff) / 255.0f) blue:(((rgb) & 0xff) / 255.0f) alpha:1.0f]) + +@interface ProgressSpinnerViewInternal : UIView + +@property (nonatomic, copy) void (^onDraw)(void); +@property (nonatomic, copy) void (^onSuccess)(void); + +- (instancetype)initWithFrame:(CGRect)frame light:(bool)light; + +- (void)setProgress; +- (void)setSucceed:(bool)fromRotation progress:(CGFloat)progress; + +@end + +@interface ProgressSpinnerView () +{ + UIImageView *_arcView; + ProgressSpinnerViewInternal *_internalView; + + bool _progressing; +} +@end + +@implementation ProgressSpinnerView + +- (instancetype)initWithFrame:(CGRect)frame light:(bool)light { + self = [super initWithFrame:frame]; + if (self != nil) { + self.backgroundColor = [UIColor clearColor]; + self.opaque = false; + self.userInteractionEnabled = false; + + UIImage *arcImage = nil; + CGRect rect = CGRectMake(0.0f, 0.0f, frame.size.width, frame.size.height); + UIGraphicsBeginImageContextWithOptions(rect.size, false, 0.0f); + CGContextRef context = UIGraphicsGetCurrentContext(); + + CGPoint centerPoint = CGPointMake(rect.size.width / 2.0f, rect.size.height / 2.0f); + CGFloat lineWidth = 4.0f; + CGFloat inset = 3.0f; + + UIColor *foregroundColor = light ? UIColorRGB(0x5a5a5a) : [UIColor whiteColor]; + CGContextSetFillColorWithColor(context, foregroundColor.CGColor); + CGContextSetStrokeColorWithColor(context, foregroundColor.CGColor); + + CGMutablePathRef path = CGPathCreateMutable(); + CGFloat offset = -2.0f * M_PI; + CGPathAddArc(path, NULL, centerPoint.x, centerPoint.y, (rect.size.width - inset * 2.0f - lineWidth) / 2.0f, offset, offset + (3.0f * M_PI_2), false); + CGPathRef strokedArc = CGPathCreateCopyByStrokingPath(path, NULL, lineWidth, kCGLineCapRound, kCGLineJoinMiter, 10); + CGContextAddPath(context, strokedArc); + CGPathRelease(strokedArc); + CGPathRelease(path); + + CGContextFillPath(context); + + arcImage = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + //}); + + _arcView = [[UIImageView alloc] initWithFrame:self.bounds]; + _arcView.image = arcImage; + _arcView.hidden = true; + [self addSubview:_arcView]; + + _internalView = [[ProgressSpinnerViewInternal alloc] initWithFrame:self.bounds light:light]; + _internalView.hidden = true; + [self addSubview:_internalView]; + } + return self; +} + +- (void)setProgress { + _arcView.hidden = false; + _progressing = true; + + CABasicAnimation *rotationAnimation; + rotationAnimation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"]; + rotationAnimation.toValue = @(-M_PI * 2.0f); + rotationAnimation.duration = 0.75; + rotationAnimation.cumulative = true; + rotationAnimation.repeatCount = HUGE_VALF; + + [_arcView.layer addAnimation:rotationAnimation forKey:@"rotationAnimation"]; +} + +- (void)setSucceed { + _internalView.hidden = false; + + if (_progressing) + { + CGFloat value = [[_arcView.layer.presentationLayer valueForKeyPath:@"transform.rotation.z"] doubleValue] / (-2 * M_PI); + [_internalView setSucceed:_progressing progress:value]; + + __weak ProgressSpinnerView *weakSelf = self; + _internalView.onDraw = ^{ + __strong ProgressSpinnerView *strongSelf = weakSelf; + if (strongSelf != nil) + strongSelf->_arcView.hidden = true; + }; + _internalView.onSuccess = ^{ + __strong ProgressSpinnerView *strongSelf = weakSelf; + if (strongSelf != nil && strongSelf.onSuccess != nil) + strongSelf.onSuccess(); + }; + } + else + { + [_internalView setSucceed:false progress:0.0f]; + } +} + +@end + +@interface ProgressSpinnerViewInternal () +{ + CADisplayLink *_displayLink; + + bool _light; + + bool _isProgressing; + CGFloat _rotationValue; + bool _isRotating; + + CGFloat _checkValue; + bool _delay; + bool _isSucceed; + bool _isChecking; + + NSTimeInterval _previousTime; +} +@end + +@implementation ProgressSpinnerViewInternal + +- (instancetype)initWithFrame:(CGRect)frame light:(bool)light { + self = [super initWithFrame:frame]; + if (self != nil) { + _light = light; + + self.backgroundColor = [UIColor clearColor]; + self.opaque = false; + self.userInteractionEnabled = false; + } + return self; +} + +- (void)dealloc { + _displayLink.paused = true; + [_displayLink removeFromRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes]; +} + +- (CADisplayLink *)displayLink { + if (_displayLink == nil) { + _displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(displayLinkUpdate)]; + _displayLink.paused = true; + [_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes]; + } + return _displayLink; +} + +- (void)drawRect:(CGRect)rect { + CGContextRef context = UIGraphicsGetCurrentContext(); + + CGPoint centerPoint = CGPointMake(rect.size.width / 2.0f, rect.size.height / 2.0f); + CGFloat lineWidth = 4.0f; + CGFloat inset = 3.0f; + if (rect.size.width < 44.0) { + inset = 0.0f; + } + + UIColor *foregroundColor = _light ? UIColorRGB(0x5a5a5a) : [UIColor whiteColor]; + CGContextSetFillColorWithColor(context, foregroundColor.CGColor); + CGContextSetStrokeColorWithColor(context, foregroundColor.CGColor); + + if (_isProgressing) + { + CGMutablePathRef path = CGPathCreateMutable(); + CGFloat offset = -_rotationValue * 2.0f * M_PI; + CGPathAddArc(path, NULL, centerPoint.x, centerPoint.y, (rect.size.width - inset * 2.0f - lineWidth) / 2.0f, offset, offset + (3.0f * M_PI_2) * (1.0f - _checkValue), false); + CGPathRef strokedArc = CGPathCreateCopyByStrokingPath(path, NULL, lineWidth, kCGLineCapRound, kCGLineJoinMiter, 10); + CGContextAddPath(context, strokedArc); + CGPathRelease(strokedArc); + CGPathRelease(path); + + CGContextFillPath(context); + } + + if (_checkValue > FLT_EPSILON) + { + CGContextSetLineWidth(context, 5.0f); + CGContextSetLineCap(context, kCGLineCapRound); + CGContextSetLineJoin(context, kCGLineJoinRound); + CGContextSetMiterLimit(context, 10); + + CGFloat firstSegment = MIN(1.0f, _checkValue * 3.0f); + CGPoint s = CGPointMake(inset + 5.0f / 2.0f, centerPoint.y); + CGPoint p1 = CGPointMake(13.0f, 13.0f); + CGPoint p2 = CGPointMake(27.0f, -27.0f); + + if (firstSegment < 1.0f) + { + CGContextMoveToPoint(context, s.x + p1.x * firstSegment, s.y + p1.y * firstSegment); + CGContextAddLineToPoint(context, s.x, s.y); + } + else + { + CGFloat secondSegment = (_checkValue - 0.33f) * 1.5f; + CGContextMoveToPoint(context, s.x + p1.x + p2.x * secondSegment, s.y + p1.y + p2.y * secondSegment); + CGContextAddLineToPoint(context, s.x + p1.x, s.y + p1.y); + CGContextAddLineToPoint(context, s.x, s.y); + } + + CGContextStrokePath(context); + } +} + +- (void)displayLinkUpdate +{ + NSTimeInterval previousTime = _previousTime; + NSTimeInterval currentTime = CACurrentMediaTime(); + _previousTime = currentTime; + + NSTimeInterval delta = previousTime > DBL_EPSILON ? currentTime - previousTime : 0.0; + if (delta < DBL_EPSILON) + return; + + if (_isRotating) + { + _rotationValue += delta * 1.35f; + } + + if (_isSucceed && _isRotating && !_delay && _rotationValue >= 0.5f) + { + _rotationValue = 0.5f; + _isRotating = false; + _isChecking = true; + } + + if (_isChecking) + _checkValue += delta * M_PI * 1.6f; + + if (_rotationValue > 1.0f) + { + _rotationValue = 0.0f; + _delay = false; + } + + if (_checkValue > 1.0f) + { + _checkValue = 1.0f; + [self displayLink].paused = true; + + if (self.onSuccess != nil) + { + void (^onSuccess)(void) = [self.onSuccess copy]; + self.onSuccess = nil; + onSuccess(); + } + } + + [self setNeedsDisplay]; + + if (self.onDraw != nil) + { + void (^onDraw)(void) = [self.onDraw copy]; + self.onDraw = nil; + onDraw(); + } +} + +- (void)setProgress { + _isRotating = true; + _isProgressing = true; + + [self displayLink].paused = false; +} + +- (void)setSucceed:(bool)fromRotation progress:(CGFloat)progress { + if (_isSucceed) + return; + + if (fromRotation) { + _isRotating = true; + _isProgressing = true; + _rotationValue = progress; + } + + _isSucceed = true; + + if (!_isRotating) + _isChecking = true; + else if (_rotationValue > 0.5f) + _delay = true; + + [self displayLink].paused = false; +} + +- (bool)isSucceed +{ + return _isSucceed; +} + +@end diff --git a/submodules/OverlayStatusController/Sources/ProgressWindow.h b/submodules/OverlayStatusController/Sources/ProgressWindow.h new file mode 100644 index 0000000000..74bcc35412 --- /dev/null +++ b/submodules/OverlayStatusController/Sources/ProgressWindow.h @@ -0,0 +1,16 @@ +#import + +@interface ProgressWindowController : UIViewController + +@property (nonatomic, copy) void (^cancelled)(void); + +- (instancetype)init; +- (instancetype)initWithLight:(bool)light; + +- (void)show:(bool)animated; +- (void)dismiss:(bool)animated completion:(void (^)(void))completion; +- (void)dismissWithSuccess:(void (^)(void))completion; + +- (void)updateLayout; + +@end diff --git a/submodules/OverlayStatusController/Sources/ProgressWindow.m b/submodules/OverlayStatusController/Sources/ProgressWindow.m new file mode 100644 index 0000000000..7dda6129be --- /dev/null +++ b/submodules/OverlayStatusController/Sources/ProgressWindow.m @@ -0,0 +1,194 @@ +#import "ProgressWindow.h" + +#import "ProgressSpinnerView.h" + +#define UIColorRGBA(rgb,a) ([[UIColor alloc] initWithRed:(((rgb >> 16) & 0xff) / 255.0f) green:(((rgb >> 8) & 0xff) / 255.0f) blue:(((rgb) & 0xff) / 255.0f) alpha:a]) + +#ifdef __LP64__ +# define CGFloor floor +#else +# define CGFloor floorf +#endif + +static inline void dispatchAfter(double delay, dispatch_queue_t queue, dispatch_block_t block) +{ + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)((delay) * NSEC_PER_SEC)), queue, block); +} + +static bool ProgressWindowIsLight = true; + +@interface ProgressWindowController () +{ + bool _light; + UIVisualEffectView *_effectView; + UIView *_backgroundView; + ProgressSpinnerView *_spinner; +} + +@property (nonatomic, weak) UIWindow *weakWindow; +@property (nonatomic, strong) UIView *containerView; + +@end + +@implementation ProgressWindowController + +- (instancetype)init { + return [self initWithLight:ProgressWindowIsLight]; +} + +- (instancetype)initWithLight:(bool)light +{ + self = [super init]; + if (self != nil) + { + _light = light; + } + return self; +} + +- (void)loadView +{ + [super loadView]; + + _containerView = [[UIView alloc] initWithFrame:CGRectMake(CGFloor(self.view.frame.size.width - 100) / 2, CGFloor(self.view.frame.size.height - 100) / 2, 100, 100)]; + _containerView.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin; + _containerView.alpha = 0.0f; + _containerView.clipsToBounds = true; + _containerView.layer.cornerRadius = 20.0f; + _containerView.userInteractionEnabled = false; + [self.view addSubview:_containerView]; + + if ([[[UIDevice currentDevice] systemVersion] intValue] >= 9) + { + _effectView = [[UIVisualEffectView alloc] initWithEffect:_light ? [UIBlurEffect effectWithStyle:UIBlurEffectStyleLight] : [UIBlurEffect effectWithStyle:UIBlurEffectStyleDark]]; + _effectView.frame = _containerView.bounds; + [_containerView addSubview:_effectView]; + + if (_light) + { + UIView *tintView = [[UIView alloc] initWithFrame:_effectView.bounds]; + tintView.backgroundColor = UIColorRGBA(0xf4f4f4, 0.75f); + [_containerView addSubview:tintView]; + } + } + else + { + _backgroundView = [[UIView alloc] initWithFrame:_containerView.bounds]; + _backgroundView.backgroundColor = _light ? UIColorRGBA(0xeaeaea, 0.92f) : UIColorRGBA(0x000000, 0.9f); + [_containerView addSubview:_backgroundView]; + } + + _spinner = [[ProgressSpinnerView alloc] initWithFrame:CGRectMake((_containerView.frame.size.width - 48.0f) / 2.0f, (_containerView.frame.size.height - 48.0f) / 2.0f, 48.0f, 48.0f) light:_light]; + [_containerView addSubview:_spinner]; + + self.view.userInteractionEnabled = true; + [self.view addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapGesture:)]]; +} + +- (void)tapGesture:(UITapGestureRecognizer *)recognizer { + if (recognizer.state == UIGestureRecognizerStateEnded) { + if (_cancelled) { + _cancelled(); + } + } +} + +- (void)updateLayout { + _containerView.frame = CGRectMake(CGFloor(self.view.frame.size.width - 100) / 2, CGFloor(self.view.frame.size.height - 100) / 2, 100, 100); + _spinner.frame = CGRectMake((_containerView.frame.size.width - 48.0f) / 2.0f, (_containerView.frame.size.height - 48.0f) / 2.0f, 48.0f, 48.0f); +} + +- (void)show:(bool)animated +{ + UIWindow *window = _weakWindow; + + window.userInteractionEnabled = true; + window.hidden = false; + + [_spinner setProgress]; + + if (animated) + { + _containerView.transform = CGAffineTransformMakeScale(0.6f, 0.6f); + [UIView animateWithDuration:0.3 delay:0.0 options:7 << 16 animations:^{ + _containerView.transform = CGAffineTransformIdentity; + } completion:nil]; + + [UIView animateWithDuration:0.3f animations:^ + { + _containerView.alpha = 1.0f; + }]; + } + else + _containerView.alpha = 1.0f; +} + +- (void)dismiss:(bool)animated { + [self dismiss:animated completion:nil]; +} + +- (void)dismiss:(bool)animated completion:(void (^)())completion +{ + if (animated) + { + [UIView animateWithDuration:0.3f delay:0 options:UIViewAnimationOptionBeginFromCurrentState animations:^ + { + _containerView.alpha = 0.0f; + } completion:^(__unused BOOL finished) + { + if (completion) { + completion(); + } + }]; + } + else + { + _containerView.alpha = 0.0f; + + if (completion) { + completion(); + } + } +} + +- (void)dismissWithSuccess:(void (^)(void))completion +{ + void (^dismissBlock)(void) = ^ + { + [UIView animateWithDuration:0.3 delay:0.55 options:0 animations:^ + { + _containerView.alpha = 0.0f; + } completion:^(BOOL finished) + { + if (finished) + { + if (completion) { + completion(); + } + } + }]; + }; + + _containerView.transform = CGAffineTransformMakeScale(0.6f, 0.6f); + + [UIView animateWithDuration:0.3 delay:0.0 options:7 << 16 animations:^{ + _containerView.transform = CGAffineTransformIdentity; + } completion:nil]; + + [UIView animateWithDuration:0.3f animations:^ + { + _containerView.alpha = 1.0f; + } completion:^(__unused BOOL finished) { + dismissBlock(); + }]; + + dispatchAfter(0.15, dispatch_get_main_queue(), ^{ + [_spinner setSucceed]; + }); +} + +- (BOOL)canBecomeFirstResponder { + return false; +} + +@end diff --git a/submodules/OverlayStatusController/Sources/ProxyWindow.h b/submodules/OverlayStatusController/Sources/ProxyWindow.h new file mode 100644 index 0000000000..2576321ac1 --- /dev/null +++ b/submodules/OverlayStatusController/Sources/ProxyWindow.h @@ -0,0 +1,12 @@ +#import + +@interface ProxyWindowController : UIViewController + +- (instancetype)initWithLight:(bool)light text:(NSString *)text icon:(UIImage *)icon isShield:(bool)isShield; + +- (void)dismissWithSuccess:(void (^)(void))completion increasedDelay:(bool)increasedDelay; +- (void)updateLayout; + ++ (UIImage *)generateShieldImage:(bool)isLight; + +@end diff --git a/submodules/OverlayStatusController/Sources/ProxyWindow.m b/submodules/OverlayStatusController/Sources/ProxyWindow.m new file mode 100644 index 0000000000..c68ddd8bc5 --- /dev/null +++ b/submodules/OverlayStatusController/Sources/ProxyWindow.m @@ -0,0 +1,573 @@ +#import "ProxyWindow.h" + +#define UIColorRGB(rgb) ([[UIColor alloc] initWithRed:(((rgb >> 16) & 0xff) / 255.0f) green:(((rgb >> 8) & 0xff) / 255.0f) blue:(((rgb) & 0xff) / 255.0f) alpha:1.0f]) +#define UIColorRGBA(rgb,a) ([[UIColor alloc] initWithRed:(((rgb >> 16) & 0xff) / 255.0f) green:(((rgb >> 8) & 0xff) / 255.0f) blue:(((rgb) & 0xff) / 255.0f) alpha:a]) + +#ifdef __LP64__ +# define CGFloor floor +#else +# define CGFloor floorf +#endif + +static inline void dispatchAfter(double delay, dispatch_queue_t queue, dispatch_block_t block) +{ + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)((delay) * NSEC_PER_SEC)), queue, block); +} + +static UIFont *mediumSystemFontOfSize(CGFloat size) { + static bool useSystem = false; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + useSystem = [[[UIDevice currentDevice] systemVersion] intValue] >= 9; + }); + + if (useSystem) { + return [UIFont systemFontOfSize:size weight:UIFontWeightMedium]; + } else { + return [UIFont fontWithName:@"HelveticaNeue-Medium" size:size]; + } +} + + +static bool readCGFloat(NSString *string, int *position, CGFloat *result) { + int start = *position; + bool seenDot = false; + int length = (int)string.length; + while (*position < length) { + unichar c = [string characterAtIndex:*position]; + *position++; + + if (c == '.') { + if (seenDot) { + return false; + } else { + seenDot = true; + } + } else if ((c < '0' || c > '9') && c != '-') { + if (*position == start) { + *result = 0.0f; + return true; + } else { + *result = [[string substringWithRange:NSMakeRange(start, *position - start)] floatValue]; + return true; + } + } + } + if (*position == start) { + *result = 0.0f; + return true; + } else { + *result = [[string substringWithRange:NSMakeRange(start, *position - start)] floatValue]; + return true; + } + return true; +} + +static void drawSvgPath(CGContextRef context, NSString *path) { + int position = 0; + int length = (int)path.length; + + while (position < length) { + unichar c = [path characterAtIndex:position]; + position++; + + if (c == ' ') { + continue; + } + + if (c == 'M') { // M + CGFloat x = 0.0f; + CGFloat y = 0.0f; + readCGFloat(path, &position, &x); + readCGFloat(path, &position, &y); + CGContextMoveToPoint(context, x, y); + } else if (c == 'L') { // L + CGFloat x = 0.0f; + CGFloat y = 0.0f; + readCGFloat(path, &position, &x); + readCGFloat(path, &position, &y); + CGContextAddLineToPoint(context, x, y); + } else if (c == 'C') { // C + CGFloat x1 = 0.0f; + CGFloat y1 = 0.0f; + CGFloat x2 = 0.0f; + CGFloat y2 = 0.0f; + CGFloat x = 0.0f; + CGFloat y = 0.0f; + readCGFloat(path, &position, &x1); + readCGFloat(path, &position, &y1); + readCGFloat(path, &position, &x2); + readCGFloat(path, &position, &y2); + readCGFloat(path, &position, &x); + readCGFloat(path, &position, &y); + + CGContextAddCurveToPoint(context, x1, y1, x2, y2, x, y); + } else if (c == 'Z') { // Z + CGContextClosePath(context); + CGContextFillPath(context); + CGContextBeginPath(context); + } else if (c == 'S') { // Z + CGContextClosePath(context); + CGContextStrokePath(context); + CGContextBeginPath(context); + } else if (c == 'U') { // Z + CGContextStrokePath(context); + CGContextBeginPath(context); + } + } +} + +static bool ProxyWindowIsLight = true; + +@interface ProxySpinnerView : UIView + +@property (nonatomic, copy) void (^onSuccess)(void); + +- (instancetype)initWithFrame:(CGRect)frame light:(bool)light; + +- (void)setSucceed; + +@end + +@interface ProxyWindowController () +{ + bool _light; + NSString *_text; + UIImage *_icon; + bool _isShield; + UIVisualEffectView *_effectView; + UIView *_backgroundView; + ProxySpinnerView *_spinner; + UIImageView *_shield; + UILabel *_label; +} + +@property (nonatomic, weak) UIWindow *weakWindow; +@property (nonatomic, strong) UIView *containerView; + +@end + +@implementation ProxyWindowController + ++ (UIImage *)generateShieldImage:(bool)isLight { + UIColor *color = isLight ? UIColorRGB(0x5a5a5a) : [UIColor whiteColor]; + + NSString *code = @"M100,6.56393754 L6,48.2657557 L6,110.909091 C6,169.509174 46.3678836,223.966692 100,237.814087 C153.632116,223.966692 194,169.509174 194,110.909091 L194,48.2657557 L100,6.56393754 S"; + + UIGraphicsBeginImageContextWithOptions(CGSizeMake(67, 82), false, 0.0f); + + CGContextRef context = UIGraphicsGetCurrentContext(); + CGContextScaleCTM(context, 0.333333f, 0.333333f); + CGContextSetLineWidth(context, 12.0f); + CGContextSetStrokeColorWithColor(context, color.CGColor); + drawSvgPath(context, code); + + UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + + return image; +} + +- (instancetype)initWithLight:(bool)light text:(NSString *)text icon:(UIImage *)icon isShield:(bool)isShield { + self = [super init]; + if (self != nil) { + _light = light; + _text = text; + _icon = icon; + _isShield = isShield; + } + return self; +} + +- (void)loadView +{ + [super loadView]; + + if (self.view.bounds.size.width > FLT_EPSILON) { + [self updateLayout]; + } +} + +- (void)updateLayout { + CGSize spinnerSize = CGSizeMake(48.0, 48.0); + CGSize containerSize = CGSizeMake(156.0, 176.0); + if (_icon == nil) { + containerSize = CGSizeMake(207.0, 177.0); + spinnerSize = CGSizeMake(40.0, 40.0); + } + + if (_text.length != 0) { + NSMutableParagraphStyle *style = [[NSMutableParagraphStyle alloc] init]; + style.lineBreakMode = NSLineBreakByWordWrapping; + style.lineSpacing = 2.0f; + style.alignment = NSTextAlignmentCenter; + + NSDictionary *attributes = @{NSForegroundColorAttributeName:_light ? UIColorRGB(0x5a5a5a) : [UIColor whiteColor], NSFontAttributeName:mediumSystemFontOfSize(17.0f), NSParagraphStyleAttributeName:style}; + NSAttributedString *string = [[NSAttributedString alloc] initWithString:_text attributes:attributes]; + + UILabel *label = [[UILabel alloc] init]; + label.font = [UIFont systemFontOfSize:15.0f]; + label.numberOfLines = 0; + label.textAlignment = NSTextAlignmentCenter; + label.attributedText = string; + CGSize labelSize = [label sizeThatFits:CGSizeMake(containerSize.width - 10.0 * 2.0, CGFLOAT_MAX)]; + + containerSize.height += labelSize.height - 38.0; + } + + CGRect spinnerFrame = CGRectMake((containerSize.width - spinnerSize.width) / 2.0f, _icon != nil ? 40.0f : 45.0, spinnerSize.width, spinnerSize.height); + if (_containerView == nil) { + _containerView = [[UIView alloc] initWithFrame:CGRectMake(CGFloor(self.view.frame.size.width - containerSize.width) / 2, CGFloor(self.view.frame.size.height - containerSize.height) / 2, containerSize.width, containerSize.height)]; + _containerView.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin; + _containerView.alpha = 0.0f; + _containerView.clipsToBounds = true; + _containerView.layer.cornerRadius = 20.0f; + [self.view addSubview:_containerView]; + + if ([[[UIDevice currentDevice] systemVersion] intValue] >= 9) { + _effectView = [[UIVisualEffectView alloc] initWithEffect:_light ? [UIBlurEffect effectWithStyle:UIBlurEffectStyleLight] : [UIBlurEffect effectWithStyle:UIBlurEffectStyleDark]]; + _effectView.frame = _containerView.bounds; + [_containerView addSubview:_effectView]; + + if (_light) + { + UIView *tintView = [[UIView alloc] initWithFrame:_effectView.bounds]; + tintView.backgroundColor = UIColorRGBA(0xf4f4f4, 0.75f); + [_containerView addSubview:tintView]; + } + } else { + _backgroundView = [[UIView alloc] initWithFrame:_containerView.bounds]; + _backgroundView.backgroundColor = _light ? UIColorRGBA(0xeaeaea, 0.92f) : UIColorRGBA(0x000000, 0.9f); + [_containerView addSubview:_backgroundView]; + } + + UIColor *color = _light ? UIColorRGB(0x5a5a5a) : [UIColor whiteColor]; + + UIImage *image = nil; + if (_icon != nil) { + image = _icon; + } else { + CGSize size = CGSizeMake(66.0, 66.0); + UIGraphicsBeginImageContextWithOptions(size, false, 0.0); + CGContextRef context = UIGraphicsGetCurrentContext(); + CGContextSetStrokeColorWithColor(context, color.CGColor); + CGFloat lineWidth = 4.0f; + CGContextSetLineWidth(context, lineWidth); + CGContextStrokeEllipseInRect(context, CGRectMake(lineWidth / 2.0f, lineWidth / 2.0f, size.width - lineWidth, size.height - lineWidth)); + image = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + } + _shield = [[UIImageView alloc] initWithImage:image]; + _shield.frame = CGRectMake((_containerView.frame.size.width - _shield.frame.size.width) / 2.0f, _isShield ? 23.0f : 30.0, _shield.frame.size.width, _shield.frame.size.height); + [_containerView addSubview:_shield]; + + _spinner = [[ProxySpinnerView alloc] initWithFrame:spinnerFrame light:_light]; + [_containerView addSubview:_spinner]; + + NSMutableParagraphStyle *style = [[NSMutableParagraphStyle alloc] init]; + style.lineBreakMode = NSLineBreakByWordWrapping; + style.lineSpacing = 2.0f; + style.alignment = NSTextAlignmentCenter; + + NSDictionary *attributes = @{NSForegroundColorAttributeName:_light ? UIColorRGB(0x5a5a5a) : [UIColor whiteColor], NSFontAttributeName:mediumSystemFontOfSize(17.0f), NSParagraphStyleAttributeName:style}; + NSAttributedString *string = [[NSAttributedString alloc] initWithString:_text attributes:attributes]; + + UILabel *label = [[UILabel alloc] init]; + label.font = [UIFont systemFontOfSize:15.0f]; + label.numberOfLines = 0; + label.textAlignment = NSTextAlignmentCenter; + label.attributedText = string; + _label = label; + CGSize labelSize = [label sizeThatFits:CGSizeMake(_containerView.frame.size.width - 10.0 * 2.0, CGFLOAT_MAX)]; + label.frame = CGRectMake((_containerView.frame.size.width - labelSize.width) / 2.0f, _containerView.frame.size.height - labelSize.height - 18.0f, labelSize.width, labelSize.height); + [_containerView addSubview:label]; + } else { + _containerView.frame = CGRectMake(CGFloor(self.view.frame.size.width - containerSize.width) / 2, CGFloor(self.view.frame.size.height - containerSize.width) / 2, containerSize.width, containerSize.height); + _effectView.frame = _containerView.bounds; + _backgroundView.frame = _containerView.bounds; + _spinner.frame = spinnerFrame; + _shield.frame = CGRectMake((_containerView.frame.size.width - _shield.frame.size.width) / 2.0f, _isShield ? 23.0f : 30.0, _shield.frame.size.width, _shield.frame.size.height); + [_label sizeToFit]; + _label.frame = CGRectMake((_containerView.frame.size.width - _label.frame.size.width) / 2.0f, _containerView.frame.size.height - _label.frame.size.height - 18.0f, _label.frame.size.width, _label.frame.size.height); + } +} + +- (void)dismissWithSuccess:(void (^)(void))completion increasedDelay:(bool)increasedDelay +{ + void (^dismissBlock)(void) = ^{ + [UIView animateWithDuration:0.3 delay:increasedDelay ? 2.1 : 0.55 options:0 animations:^{ + _containerView.alpha = 0.0f; + } completion:^(__unused BOOL finished) { + if (completion) { + completion(); + } + }]; + }; + + _containerView.transform = CGAffineTransformMakeScale(0.6f, 0.6f); + + [UIView animateWithDuration:0.3 delay:0.0 options:7 << 16 animations:^{ + _containerView.transform = CGAffineTransformIdentity; + } completion:nil]; + + [UIView animateWithDuration:0.3f animations:^{ + _containerView.alpha = 1.0f; + } completion:^(__unused BOOL finished) { + dismissBlock(); + }]; + + if (_icon == nil) { + dispatchAfter(0.15, dispatch_get_main_queue(), ^{ + [_spinner setSucceed]; + }); + } +} + +- (BOOL)canBecomeFirstResponder { + return false; +} + +@end + +@interface ProxySpinnerViewInternal : UIView + +@property (nonatomic, copy) void (^onDraw)(void); +@property (nonatomic, copy) void (^onSuccess)(void); + +- (instancetype)initWithFrame:(CGRect)frame light:(bool)light; + +- (void)setSucceed:(bool)fromRotation progress:(CGFloat)progress; + +@end + +@interface ProxySpinnerView () +{ + ProxySpinnerViewInternal *_internalView; + + bool _progressing; +} +@end + +@implementation ProxySpinnerView + +- (instancetype)initWithFrame:(CGRect)frame light:(bool)light { + self = [super initWithFrame:frame]; + if (self != nil) { + self.backgroundColor = [UIColor clearColor]; + self.opaque = false; + self.userInteractionEnabled = false; + + _internalView = [[ProxySpinnerViewInternal alloc] initWithFrame:self.bounds light:light]; + _internalView.hidden = true; + [self addSubview:_internalView]; + } + return self; +} + +- (void)setSucceed { + _internalView.hidden = false; + + [_internalView setSucceed:false progress:0.0f]; +} + +@end + +@interface ProxySpinnerViewInternal () +{ + CADisplayLink *_displayLink; + + bool _light; + + bool _isProgressing; + CGFloat _rotationValue; + bool _isRotating; + + CGFloat _checkValue; + bool _delay; + bool _isSucceed; + bool _isChecking; + + NSTimeInterval _previousTime; +} +@end + +@implementation ProxySpinnerViewInternal + +- (instancetype)initWithFrame:(CGRect)frame light:(bool)light { + self = [super initWithFrame:frame]; + if (self != nil) { + _light = light; + + self.backgroundColor = [UIColor clearColor]; + self.opaque = false; + self.userInteractionEnabled = false; + } + return self; +} + +- (void)dealloc { + _displayLink.paused = true; + [_displayLink removeFromRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes]; +} + +- (CADisplayLink *)displayLink { + if (_displayLink == nil) { + _displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(displayLinkUpdate)]; + _displayLink.paused = true; + [_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes]; + } + return _displayLink; +} + +- (void)drawRect:(CGRect)rect { + CGContextRef context = UIGraphicsGetCurrentContext(); + + CGPoint centerPoint = CGPointMake(rect.size.width / 2.0f, rect.size.height / 2.0f); + CGFloat lineWidth = 4.0f; + CGFloat inset = 3.0f; + if (rect.size.width < 44.0) { + inset = 0.0f; + } + + UIColor *foregroundColor = _light ? UIColorRGB(0x5a5a5a) : [UIColor whiteColor]; + CGContextSetFillColorWithColor(context, foregroundColor.CGColor); + CGContextSetStrokeColorWithColor(context, foregroundColor.CGColor); + + if (_isProgressing) + { + CGMutablePathRef path = CGPathCreateMutable(); + CGFloat offset = -_rotationValue * 2.0f * M_PI; + CGPathAddArc(path, NULL, centerPoint.x, centerPoint.y, (rect.size.width - inset * 2.0f - lineWidth) / 2.0f, offset, offset + (3.0f * M_PI_2) * (1.0f - _checkValue), false); + CGPathRef strokedArc = CGPathCreateCopyByStrokingPath(path, NULL, lineWidth, kCGLineCapRound, kCGLineJoinMiter, 10); + CGContextAddPath(context, strokedArc); + CGPathRelease(strokedArc); + CGPathRelease(path); + + CGContextFillPath(context); + } + + if (_checkValue > FLT_EPSILON) + { + CGContextSetLineWidth(context, 4.0f); + CGContextSetLineCap(context, kCGLineCapRound); + CGContextSetLineJoin(context, kCGLineJoinRound); + CGContextSetMiterLimit(context, 10); + + CGFloat firstSegment = MIN(1.0f, _checkValue * 3.0f); + CGPoint s = CGPointMake(inset + 5.0f, centerPoint.y + 1.0f); + CGPoint p1 = CGPointMake(10.0f, 10.0f); + CGPoint p2 = CGPointMake(23.0f, -23.0f); + if (rect.size.width < 44.0) { + p1 = CGPointMake(9.0f, 9.0f); + p2 = CGPointMake(23.0f, -23.0f); + } + + if (firstSegment < 1.0f) + { + CGContextMoveToPoint(context, s.x + p1.x * firstSegment, s.y + p1.y * firstSegment); + CGContextAddLineToPoint(context, s.x, s.y); + } + else + { + CGFloat secondSegment = (_checkValue - 0.33f) * 1.5f; + if (rect.size.width < 44.0) { + secondSegment = (_checkValue - 0.33f) * 1.35f; + } + CGContextMoveToPoint(context, s.x + p1.x + p2.x * secondSegment, s.y + p1.y + p2.y * secondSegment); + CGContextAddLineToPoint(context, s.x + p1.x, s.y + p1.y); + CGContextAddLineToPoint(context, s.x, s.y); + } + + CGContextStrokePath(context); + } +} + +- (void)displayLinkUpdate +{ + NSTimeInterval previousTime = _previousTime; + NSTimeInterval currentTime = CACurrentMediaTime(); + _previousTime = currentTime; + + NSTimeInterval delta = previousTime > DBL_EPSILON ? currentTime - previousTime : 0.0; + if (delta < DBL_EPSILON) + return; + + if (_isRotating) + { + _rotationValue += delta * 1.35f; + } + + if (_isSucceed && _isRotating && !_delay && _rotationValue >= 0.5f) + { + _rotationValue = 0.5f; + _isRotating = false; + _isChecking = true; + } + + if (_isChecking) + _checkValue += delta * M_PI * 1.6f; + + if (_rotationValue > 1.0f) + { + _rotationValue = 0.0f; + _delay = false; + } + + if (_checkValue > 1.0f) + { + _checkValue = 1.0f; + [self displayLink].paused = true; + + if (self.onSuccess != nil) + { + void (^onSuccess)(void) = [self.onSuccess copy]; + self.onSuccess = nil; + onSuccess(); + } + } + + [self setNeedsDisplay]; + + if (self.onDraw != nil) + { + void (^onDraw)(void) = [self.onDraw copy]; + self.onDraw = nil; + onDraw(); + } +} + +- (void)setProgress { + _isRotating = true; + _isProgressing = true; + + [self displayLink].paused = false; +} + +- (void)setSucceed:(bool)fromRotation progress:(CGFloat)progress { + if (_isSucceed) + return; + + if (fromRotation) { + _isRotating = true; + _isProgressing = true; + _rotationValue = progress; + } + + _isSucceed = true; + + if (!_isRotating) + _isChecking = true; + else if (_rotationValue > 0.5f) + _delay = true; + + [self displayLink].paused = false; +} + +- (bool)isSucceed +{ + return _isSucceed; +} + +@end + + diff --git a/submodules/PassportUI/BUCK b/submodules/PassportUI/BUCK index 6910b1af26..fddf791f3f 100644 --- a/submodules/PassportUI/BUCK +++ b/submodules/PassportUI/BUCK @@ -23,6 +23,7 @@ static_library( "//submodules/PasswordSetupUI:PasswordSetupUI", "//submodules/AppBundle:AppBundle", "//submodules/PresentationDataUtils:PresentationDataUtils", + "//submodules/Markdown:Markdown", ], frameworks = [ "$SDKROOT/System/Library/Frameworks/Foundation.framework", diff --git a/submodules/PassportUI/Sources/SecureIdAuthController.swift b/submodules/PassportUI/Sources/SecureIdAuthController.swift index bc68678938..2e1a240525 100644 --- a/submodules/PassportUI/Sources/SecureIdAuthController.swift +++ b/submodules/PassportUI/Sources/SecureIdAuthController.swift @@ -332,7 +332,7 @@ public final class SecureIdAuthController: ViewController { return } - let item = UIBarButtonItem(customDisplayNode: ProgressNavigationButtonNode(theme: strongSelf.presentationData.theme)) + let item = UIBarButtonItem(customDisplayNode: ProgressNavigationButtonNode(color: strongSelf.presentationData.theme.rootController.navigationBar.controlColor)) strongSelf.navigationItem.rightBarButtonItem = item strongSelf.deleteDisposable.set((deleteSecureIdValues(network: strongSelf.context.account.network, keys: Set(values.map({ $0.value.key }))) |> deliverOnMainQueue).start(completed: { @@ -387,7 +387,7 @@ public final class SecureIdAuthController: ViewController { if previousHadProgress != updatedHasProgress { if updatedHasProgress { - let item = UIBarButtonItem(customDisplayNode: ProgressNavigationButtonNode(theme: self.presentationData.theme)) + let item = UIBarButtonItem(customDisplayNode: ProgressNavigationButtonNode(color: self.presentationData.theme.rootController.navigationBar.controlColor)) self.navigationItem.rightBarButtonItem = item } else { self.navigationItem.rightBarButtonItem = UIBarButtonItem(image: PresentationResourcesRootController.navigationInfoIcon(self.presentationData.theme), style: .plain, target: self, action: #selector(self.infoPressed)) diff --git a/submodules/PassportUI/Sources/SecureIdAuthFormContentNode.swift b/submodules/PassportUI/Sources/SecureIdAuthFormContentNode.swift index 2555e374f7..2fcb17ecae 100644 --- a/submodules/PassportUI/Sources/SecureIdAuthFormContentNode.swift +++ b/submodules/PassportUI/Sources/SecureIdAuthFormContentNode.swift @@ -6,6 +6,7 @@ import Postbox import TelegramCore import TelegramPresentationData import TextFormat +import Markdown private let infoFont = Font.regular(14.0) private let passwordFont = Font.regular(16.0) diff --git a/submodules/PassportUI/Sources/SecureIdDocumentFormController.swift b/submodules/PassportUI/Sources/SecureIdDocumentFormController.swift index 5a18bf37a5..39f6de0509 100644 --- a/submodules/PassportUI/Sources/SecureIdDocumentFormController.swift +++ b/submodules/PassportUI/Sources/SecureIdDocumentFormController.swift @@ -118,7 +118,7 @@ final class SecureIdDocumentFormController: FormController UIImage? { return generateImage(CGSize(width: diameter + shadowBlur * 2.0, height: diameter + shadowBlur * 2.0), rotatedContext: { size, context in @@ -135,7 +136,7 @@ final class ReactionNode: ASDisplayNode { renderSize = CGSize(width: intrinsicSize.width * 2.5, height: intrinsicSize.height * 2.5) } } - self.animationNode.setup(resource: .localFile(path), width: Int(renderSize.width), height: Int(renderSize.height), mode: .direct) + self.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(path: path), width: Int(renderSize.width), height: Int(renderSize.height), mode: .direct) case .reply: self.intrinsicOffset = CGPoint(x: 0.0, y: 0.0) self.imageNode.image = UIImage(named: "Chat/Context Menu/ReactionReply", in: getAppBundle(), compatibleWith: nil) diff --git a/submodules/SettingsUI/BUCK b/submodules/SettingsUI/BUCK index fe9149899e..1a7bffaa8a 100644 --- a/submodules/SettingsUI/BUCK +++ b/submodules/SettingsUI/BUCK @@ -78,6 +78,7 @@ static_library( "//submodules/AppBundle:AppBundle", "//submodules/ContextUI:ContextUI", "//submodules/WalletUI:WalletUI", + "//submodules/Markdown:Markdown", ], frameworks = [ "$SDKROOT/System/Library/Frameworks/Foundation.framework", diff --git a/submodules/SettingsUI/Sources/ChangePhoneNumberIntroController.swift b/submodules/SettingsUI/Sources/ChangePhoneNumberIntroController.swift index 193936bd8b..11f0cfc946 100644 --- a/submodules/SettingsUI/Sources/ChangePhoneNumberIntroController.swift +++ b/submodules/SettingsUI/Sources/ChangePhoneNumberIntroController.swift @@ -9,6 +9,7 @@ import AccountContext import AlertUI import PresentationDataUtils import AppBundle +import Markdown private final class ChangePhoneNumberIntroControllerNode: ASDisplayNode { var presentationData: PresentationData diff --git a/submodules/SettingsUI/Sources/LogoutOptionsController.swift b/submodules/SettingsUI/Sources/LogoutOptionsController.swift index c8d7bbf311..63b633f971 100644 --- a/submodules/SettingsUI/Sources/LogoutOptionsController.swift +++ b/submodules/SettingsUI/Sources/LogoutOptionsController.swift @@ -218,10 +218,7 @@ func logoutOptionsController(context: AccountContext, navigationController: Navi presentControllerImpl?(alertController, nil) }) - let hasWallets = availableWallets(postbox: context.account.postbox) - |> map { wallets in - return !wallets.wallets.isEmpty - } + let hasWallets = context.hasWallets let signal = combineLatest(queue: .mainQueue(), context.sharedContext.presentationData, diff --git a/submodules/SettingsUI/Sources/Privacy and Security/TwoStepVerificationResetController.swift b/submodules/SettingsUI/Sources/Privacy and Security/TwoStepVerificationResetController.swift index 7e0ec4aaa6..8d88e326b6 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/TwoStepVerificationResetController.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/TwoStepVerificationResetController.swift @@ -11,6 +11,7 @@ import TextFormat import AccountContext import AlertUI import PresentationDataUtils +import Markdown private final class TwoStepVerificationResetControllerArguments { let updateEntryText: (String) -> Void diff --git a/submodules/SettingsUI/Sources/Privacy and Security/TwoStepVerificationUnlockController.swift b/submodules/SettingsUI/Sources/Privacy and Security/TwoStepVerificationUnlockController.swift index 0c4ff4afc1..dee7a2a88c 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/TwoStepVerificationUnlockController.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/TwoStepVerificationUnlockController.swift @@ -13,6 +13,7 @@ import AccountContext import AlertUI import PresentationDataUtils import PasswordSetupUI +import Markdown private final class TwoStepVerificationUnlockSettingsControllerArguments { let updatePasswordText: (String) -> Void diff --git a/submodules/SettingsUI/Sources/SettingsController.swift b/submodules/SettingsUI/Sources/SettingsController.swift index 94cb9cc264..e117e8d3ba 100644 --- a/submodules/SettingsUI/Sources/SettingsController.swift +++ b/submodules/SettingsUI/Sources/SettingsController.swift @@ -1172,9 +1172,7 @@ public func settingsController(context: AccountContext, accountManager: AccountM return context.account.postbox.preferencesView(keys: [PreferencesKeys.appConfiguration]) |> map { view -> Bool in if #available(iOSApplicationExtension 10.3, iOS 10.3, *) { - let appConfiguration = view.values[PreferencesKeys.appConfiguration] as? AppConfiguration ?? .defaultValue - let configuration = WalletConfiguration.with(appConfiguration: appConfiguration) - return configuration.config != nil + return true } else { return false } diff --git a/submodules/SolidRoundedButtonNode/BUCK b/submodules/SolidRoundedButtonNode/BUCK index 4dc8eed041..3ef457d48d 100644 --- a/submodules/SolidRoundedButtonNode/BUCK +++ b/submodules/SolidRoundedButtonNode/BUCK @@ -8,7 +8,6 @@ static_library( deps = [ "//submodules/AsyncDisplayKit:AsyncDisplayKit#shared", "//submodules/Display:Display#shared", - "//submodules/TelegramPresentationData:TelegramPresentationData", ], frameworks = [ "$SDKROOT/System/Library/Frameworks/Foundation.framework", diff --git a/submodules/SolidRoundedButtonNode/Sources/SolidRoundedButtonNode.swift b/submodules/SolidRoundedButtonNode/Sources/SolidRoundedButtonNode.swift index 815f2a20ac..b2d3675881 100644 --- a/submodules/SolidRoundedButtonNode/Sources/SolidRoundedButtonNode.swift +++ b/submodules/SolidRoundedButtonNode/Sources/SolidRoundedButtonNode.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import AsyncDisplayKit import Display -import TelegramPresentationData private let textFont: UIFont = Font.regular(16.0) @@ -16,12 +15,6 @@ public final class SolidRoundedButtonTheme { } } -public extension SolidRoundedButtonTheme { - convenience init(theme: PresentationTheme) { - self.init(backgroundColor: theme.list.itemCheckColors.fillColor, foregroundColor: theme.list.itemCheckColors.foregroundColor) - } -} - public final class SolidRoundedButtonNode: ASDisplayNode { private var theme: SolidRoundedButtonTheme diff --git a/submodules/StickerPackPreviewUI/BUCK b/submodules/StickerPackPreviewUI/BUCK index 5aa327252d..6058deb7bd 100644 --- a/submodules/StickerPackPreviewUI/BUCK +++ b/submodules/StickerPackPreviewUI/BUCK @@ -21,7 +21,8 @@ static_library( "//submodules/TextFormat:TextFormat", "//submodules/MergeLists:MergeLists", "//submodules/ActivityIndicator:ActivityIndicator", - "//submodules/AnimationUI:AnimationUI", + "//submodules/AnimatedStickerNode:AnimatedStickerNode", + "//submodules/TelegramAnimatedStickerNode:TelegramAnimatedStickerNode", ], frameworks = [ "$SDKROOT/System/Library/Frameworks/Foundation.framework", diff --git a/submodules/StickerPackPreviewUI/Sources/StickerPackPreviewGridItem.swift b/submodules/StickerPackPreviewUI/Sources/StickerPackPreviewGridItem.swift index 18c14c4ecf..942ef827bd 100644 --- a/submodules/StickerPackPreviewUI/Sources/StickerPackPreviewGridItem.swift +++ b/submodules/StickerPackPreviewUI/Sources/StickerPackPreviewGridItem.swift @@ -7,7 +7,8 @@ import AsyncDisplayKit import Postbox import StickerResources import AccountContext -import AnimationUI +import AnimatedStickerNode +import TelegramAnimatedStickerNode final class StickerPackPreviewInteraction { var previewedItem: StickerPreviewPeekItem? @@ -108,7 +109,7 @@ final class StickerPackPreviewGridItemNode: GridItemNode { } } let fittedDimensions = dimensions.aspectFitted(CGSize(width: 160.0, height: 160.0)) - self.animationNode?.setup(resource: .resource(account, stickerItem.file.resource), width: Int(fittedDimensions.width), height: Int(fittedDimensions.height), mode: .cached) + self.animationNode?.setup(source: AnimatedStickerResourceSource(account: account, resource: stickerItem.file.resource), width: Int(fittedDimensions.width), height: Int(fittedDimensions.height), mode: .cached) self.animationNode?.visibility = self.isVisibleInGrid && self.interaction?.playAnimatedStickers ?? true self.stickerFetchedDisposable.set(freeMediaFileResourceInteractiveFetched(account: account, fileReference: stickerPackFileReference(stickerItem.file), resource: stickerItem.file.resource).start()) } else { diff --git a/submodules/StickerPackPreviewUI/Sources/StickerPreviewPeekContent.swift b/submodules/StickerPackPreviewUI/Sources/StickerPreviewPeekContent.swift index a075e54299..c56daa5f91 100644 --- a/submodules/StickerPackPreviewUI/Sources/StickerPreviewPeekContent.swift +++ b/submodules/StickerPackPreviewUI/Sources/StickerPreviewPeekContent.swift @@ -6,7 +6,8 @@ import Postbox import TelegramCore import SwiftSignalKit import StickerResources -import AnimationUI +import AnimatedStickerNode +import TelegramAnimatedStickerNode public enum StickerPreviewPeekItem: Equatable { case pack(StickerPackItem) @@ -91,7 +92,7 @@ private final class StickerPreviewPeekContentNode: ASDisplayNode, PeekController let dimensions = item.file.dimensions ?? CGSize(width: 512.0, height: 512.0) let fittedDimensions = dimensions.aspectFitted(CGSize(width: 400.0, height: 400.0)) - self.animationNode?.setup(resource: .resource(account, item.file.resource), width: Int(fittedDimensions.width), height: Int(fittedDimensions.height), mode: .direct) + self.animationNode?.setup(source: AnimatedStickerResourceSource(account: account, resource: item.file.resource), width: Int(fittedDimensions.width), height: Int(fittedDimensions.height), mode: .direct) self.animationNode?.visibility = true self.animationNode?.addSubnode(self.textNode) } else { diff --git a/submodules/TelegramAnimatedStickerNode/BUCK b/submodules/TelegramAnimatedStickerNode/BUCK new file mode 100644 index 0000000000..e9b1668421 --- /dev/null +++ b/submodules/TelegramAnimatedStickerNode/BUCK @@ -0,0 +1,24 @@ +load("//Config:buck_rule_macros.bzl", "static_library") + +static_library( + name = "TelegramAnimatedStickerNode", + srcs = glob([ + "Sources/**/*.swift", + ]), + deps = [ + "//submodules/Postbox:Postbox#shared", + "//submodules/TelegramCore:TelegramCore#shared", + "//submodules/StickerResources:StickerResources", + "//submodules/MediaResources:MediaResources", + "//submodules/Tuples:Tuples", + "//submodules/AnimatedStickerNode:AnimatedStickerNode", + "//submodules/rlottie:RLottieBinding", + "//submodules/YuvConversion:YuvConversion", + "//submodules/GZip:GZip", + ], + frameworks = [ + "$SDKROOT/System/Library/Frameworks/Foundation.framework", + "$SDKROOT/System/Library/Frameworks/UIKit.framework", + "$SDKROOT/System/Library/Frameworks/MobileCoreServices.framework", + ], +) diff --git a/submodules/AnimationUI/Sources/AnimatedStickerUtils.swift b/submodules/TelegramAnimatedStickerNode/Sources/AnimatedStickerUtils.swift similarity index 100% rename from submodules/AnimationUI/Sources/AnimatedStickerUtils.swift rename to submodules/TelegramAnimatedStickerNode/Sources/AnimatedStickerUtils.swift diff --git a/submodules/TelegramAnimatedStickerNode/Sources/TelegramAnimatedStickerNode.swift b/submodules/TelegramAnimatedStickerNode/Sources/TelegramAnimatedStickerNode.swift new file mode 100644 index 0000000000..291ead72e2 --- /dev/null +++ b/submodules/TelegramAnimatedStickerNode/Sources/TelegramAnimatedStickerNode.swift @@ -0,0 +1,39 @@ +import Foundation +import AnimatedStickerNode +import SwiftSignalKit +import Postbox +import TelegramCore +import MediaResources +import StickerResources + +public final class AnimatedStickerResourceSource: AnimatedStickerNodeSource { + public let account: Account + public let resource: MediaResource + public let fitzModifier: EmojiFitzModifier? + + public init(account: Account, resource: MediaResource, fitzModifier: EmojiFitzModifier? = nil) { + self.account = account + self.resource = resource + self.fitzModifier = fitzModifier + } + + public func cachedDataPath(width: Int, height: Int) -> Signal { + return chatMessageAnimationData(postbox: self.account.postbox, resource: self.resource, fitzModifier: self.fitzModifier, width: width, height: height, synchronousLoad: false) + |> filter { data in + return data.complete + } + |> map { data -> String in + return data.path + } + } + + public func directDataPath() -> Signal { + return self.account.postbox.mediaBox.resourceData(resource) + |> filter { data in + return data.complete + } + |> map { data -> String in + return data.path + } + } +} diff --git a/submodules/TelegramBaseController/BUCK b/submodules/TelegramBaseController/BUCK index 794ea2c3cc..c9bdcf90c8 100644 --- a/submodules/TelegramBaseController/BUCK +++ b/submodules/TelegramBaseController/BUCK @@ -18,6 +18,7 @@ static_library( "//submodules/LegacyComponents:LegacyComponents", "//submodules/OverlayStatusController:OverlayStatusController", "//submodules/PresentationDataUtils:PresentationDataUtils", + "//submodules/Markdown:Markdown", ], frameworks = [ "$SDKROOT/System/Library/Frameworks/Foundation.framework", diff --git a/submodules/TelegramBaseController/Sources/LocationBroadcastNavigationAccessoryPanel.swift b/submodules/TelegramBaseController/Sources/LocationBroadcastNavigationAccessoryPanel.swift index 7a7557828e..c5f1ee8aa5 100644 --- a/submodules/TelegramBaseController/Sources/LocationBroadcastNavigationAccessoryPanel.swift +++ b/submodules/TelegramBaseController/Sources/LocationBroadcastNavigationAccessoryPanel.swift @@ -6,6 +6,7 @@ import TelegramCore import Postbox import TelegramPresentationData import TextFormat +import Markdown private let titleFont = Font.regular(12.0) private let subtitleFont = Font.regular(10.0) diff --git a/submodules/TelegramCore/TelegramCore/Wallets.swift b/submodules/TelegramCore/TelegramCore/Wallets.swift index cc89ab1d2b..89a6c35f35 100644 --- a/submodules/TelegramCore/TelegramCore/Wallets.swift +++ b/submodules/TelegramCore/TelegramCore/Wallets.swift @@ -11,1077 +11,6 @@ import MtProtoKit import TelegramApi #endif -public struct TonKeychainEncryptedData: Codable, Equatable { - public let publicKey: Data - public let data: Data - - public init(publicKey: Data, data: Data) { - self.publicKey = publicKey - self.data = data - } -} - -public enum TonKeychainEncryptDataError { - case generic -} - -public enum TonKeychainDecryptDataError { - case generic - case publicKeyMismatch - case cancelled -} - -public struct TonKeychain { - public let encryptionPublicKey: () -> Signal - public let encrypt: (Data) -> Signal - public let decrypt: (TonKeychainEncryptedData) -> Signal - - public init(encryptionPublicKey: @escaping () -> Signal, encrypt: @escaping (Data) -> Signal, decrypt: @escaping (TonKeychainEncryptedData) -> Signal) { - self.encryptionPublicKey = encryptionPublicKey - self.encrypt = encrypt - self.decrypt = decrypt - } -} - -private final class TonInstanceImpl { - private let queue: Queue - private let basePath: String - private let config: String - private let blockchainName: String - private let network: Network? - private var instance: TON? - - init(queue: Queue, basePath: String, config: String, blockchainName: String, network: Network?) { - self.queue = queue - self.basePath = basePath - self.config = config - self.blockchainName = blockchainName - self.network = network - } - - func withInstance(_ f: (TON) -> Void) { - let instance: TON - if let current = self.instance { - instance = current - } else { - let network = self.network - instance = TON(keystoreDirectory: self.basePath + "/ton-keystore", config: self.config, blockchainName: self.blockchainName, performExternalRequest: { request in - if let network = network { - Logger.shared.log("TON Proxy", "request: \(request.data.count)") - let _ = ( - network.request(Api.functions.wallet.sendLiteRequest(body: Buffer(data: request.data))) - |> timeout(20.0, queue: .concurrentDefaultQueue(), alternate: .fail(MTRpcError(errorCode: 500, errorDescription: "NETWORK_ERROR"))) - ).start(next: { result in - switch result { - case let .liteResponse(response): - let data = response.makeData() - Logger.shared.log("TON Proxy", "response: \(data.count)") - request.onResult(data, nil) - } - }, error: { error in - request.onResult(nil, error.errorDescription) - }) - } else { - request.onResult(nil, "NETWORK_DISABLED") - } - }, enableExternalRequests: network != nil) - self.instance = instance - } - f(instance) - } -} - -public final class TonInstance { - private let queue: Queue - private let impl: QueueLocalObject - - public init(basePath: String, config: String, blockchainName: String, network: Network?) { - self.queue = .mainQueue() - let queue = self.queue - self.impl = QueueLocalObject(queue: queue, generate: { - return TonInstanceImpl(queue: queue, basePath: basePath, config: config, blockchainName: blockchainName, network: network) - }) - } - - fileprivate func exportKey(key: TONKey, localPassword: Data) -> Signal<[String], NoError> { - return Signal { subscriber in - let disposable = MetaDisposable() - - self.impl.with { impl in - impl.withInstance { ton in - let cancel = ton.export(key, localPassword: localPassword).start(next: { wordList in - guard let wordList = wordList as? [String] else { - assertionFailure() - return - } - subscriber.putNext(wordList) - subscriber.putCompletion() - }) - disposable.set(ActionDisposable { - cancel?.dispose() - }) - } - } - - return disposable - } - } - - fileprivate func createWallet(keychain: TonKeychain, localPassword: Data) -> Signal<(WalletInfo, [String]), CreateWalletError> { - return Signal { subscriber in - let disposable = MetaDisposable() - self.impl.with { impl in - impl.withInstance { ton in - let cancel = ton.createKey(withLocalPassword: localPassword, mnemonicPassword: Data()).start(next: { key in - guard let key = key as? TONKey else { - assertionFailure() - return - } - let cancel = keychain.encrypt(key.secret).start(next: { encryptedSecretData in - let _ = self.exportKey(key: key, localPassword: localPassword).start(next: { wordList in - subscriber.putNext((WalletInfo(publicKey: WalletPublicKey(rawValue: key.publicKey), encryptedSecret: encryptedSecretData), wordList)) - subscriber.putCompletion() - }, error: { error in - subscriber.putError(.generic) - }) - }, error: { _ in - subscriber.putError(.generic) - }, completed: { - }) - }, error: { _ in - }, completed: { - }) - disposable.set(ActionDisposable { - cancel?.dispose() - }) - } - } - - return disposable - } - } - - fileprivate func importWallet(keychain: TonKeychain, wordList: [String], localPassword: Data) -> Signal { - return Signal { subscriber in - let disposable = MetaDisposable() - - self.impl.with { impl in - impl.withInstance { ton in - let cancel = ton.importKey(withLocalPassword: localPassword, mnemonicPassword: Data(), wordList: wordList).start(next: { key in - guard let key = key as? TONKey else { - subscriber.putError(.generic) - return - } - let cancel = keychain.encrypt(key.secret).start(next: { encryptedSecretData in - subscriber.putNext(WalletInfo(publicKey: WalletPublicKey(rawValue: key.publicKey), encryptedSecret: encryptedSecretData)) - subscriber.putCompletion() - }, error: { _ in - subscriber.putError(.generic) - }, completed: { - }) - }, error: { _ in - subscriber.putError(.generic) - }, completed: { - }) - disposable.set(ActionDisposable { - cancel?.dispose() - }) - } - } - - return disposable - } - } - - fileprivate func walletAddress(publicKey: WalletPublicKey) -> Signal { - return Signal { subscriber in - let disposable = MetaDisposable() - - self.impl.with { impl in - impl.withInstance { ton in - let cancel = ton.getWalletAccountAddress(withPublicKey: publicKey.rawValue).start(next: { address in - guard let address = address as? String else { - return - } - subscriber.putNext(address) - subscriber.putCompletion() - }, error: { _ in - }, completed: { - }) - disposable.set(ActionDisposable { - cancel?.dispose() - }) - } - } - - return disposable - } - } - - private func getWalletStateRaw(address: String) -> Signal { - return Signal { subscriber in - let disposable = MetaDisposable() - - self.impl.with { impl in - impl.withInstance { ton in - let cancel = ton.getAccountState(withAddress: address).start(next: { state in - guard let state = state as? TONAccountState else { - return - } - subscriber.putNext(state) - }, error: { error in - if let error = error as? TONError { - if error.text.hasPrefix("LITE_SERVER_") { - subscriber.putError(.network) - } else { - subscriber.putError(.generic) - } - } else { - subscriber.putError(.generic) - } - }, completed: { - subscriber.putCompletion() - }) - disposable.set(ActionDisposable { - cancel?.dispose() - }) - } - } - - return disposable - } - } - - fileprivate func getWalletState(address: String) -> Signal<(WalletState, Int64), GetWalletStateError> { - return self.getWalletStateRaw(address: address) - |> map { state in - return (WalletState(balance: state.balance, lastTransactionId: state.lastTransactionId.flatMap(WalletTransactionId.init(tonTransactionId:))), state.syncUtime) - } - } - - fileprivate func walletLastTransactionId(address: String) -> Signal { - return Signal { subscriber in - let disposable = MetaDisposable() - - self.impl.with { impl in - impl.withInstance { ton in - let cancel = ton.getAccountState(withAddress: address).start(next: { state in - guard let state = state as? TONAccountState else { - subscriber.putNext(nil) - return - } - subscriber.putNext(state.lastTransactionId.flatMap(WalletTransactionId.init(tonTransactionId:))) - }, error: { error in - if let error = error as? TONError { - if error.text.hasPrefix("ДITE_SERVER_") { - subscriber.putError(.network) - } else { - subscriber.putError(.generic) - } - } else { - subscriber.putError(.generic) - } - }, completed: { - subscriber.putCompletion() - }) - disposable.set(ActionDisposable { - cancel?.dispose() - }) - } - } - - return disposable - } - } - - fileprivate func getWalletTransactions(address: String, previousId: WalletTransactionId) -> Signal<[WalletTransaction], GetWalletTransactionsError> { - return Signal { subscriber in - let disposable = MetaDisposable() - - self.impl.with { impl in - impl.withInstance { ton in - let cancel = ton.getTransactionList(withAddress: address, lt: previousId.lt, hash: previousId.transactionHash).start(next: { transactions in - guard let transactions = transactions as? [TONTransaction] else { - subscriber.putError(.generic) - return - } - subscriber.putNext(transactions.map(WalletTransaction.init(tonTransaction:))) - }, error: { error in - if let error = error as? TONError { - if error.text.hasPrefix("LITE_SERVER_") { - subscriber.putError(.network) - } else { - subscriber.putError(.generic) - } - } else { - subscriber.putError(.generic) - } - }, completed: { - subscriber.putCompletion() - }) - disposable.set(ActionDisposable { - cancel?.dispose() - }) - } - } - - return disposable - } - } - - fileprivate func sendGramsFromWallet(decryptedSecret: Data, localPassword: Data, walletInfo: WalletInfo, fromAddress: String, toAddress: String, amount: Int64, textMessage: Data, forceIfDestinationNotInitialized: Bool, timeout: Int32, randomId: Int64) -> Signal { - let key = TONKey(publicKey: walletInfo.publicKey.rawValue, secret: decryptedSecret) - return Signal { subscriber in - let disposable = MetaDisposable() - - self.impl.with { impl in - impl.withInstance { ton in - let cancel = ton.sendGrams(from: key, localPassword: localPassword, fromAddress: fromAddress, toAddress: toAddress, amount: amount, textMessage: textMessage, forceIfDestinationNotInitialized: forceIfDestinationNotInitialized, timeout: timeout, randomId: randomId).start(next: { result in - guard let result = result as? TONSendGramsResult else { - subscriber.putError(.generic) - return - } - subscriber.putNext(PendingWalletTransaction(timestamp: Int64(Date().timeIntervalSince1970), validUntilTimestamp: result.sentUntil, bodyHash: result.bodyHash, address: toAddress, value: amount, comment: textMessage)) - subscriber.putCompletion() - }, error: { error in - if let error = error as? TONError { - if error.text.hasPrefix("INVALID_ACCOUNT_ADDRESS") { - subscriber.putError(.invalidAddress) - } else if error.text.hasPrefix("DANGEROUS_TRANSACTION") { - subscriber.putError(.destinationIsNotInitialized) - } else if error.text.hasPrefix("MESSAGE_TOO_LONG") { - subscriber.putError(.messageTooLong) - } else if error.text.hasPrefix("NOT_ENOUGH_FUNDS") { - subscriber.putError(.notEnoughFunds) - } else if error.text.hasPrefix("LITE_SERVER_") { - subscriber.putError(.network) - } else { - subscriber.putError(.generic) - } - } else { - subscriber.putError(.generic) - } - }, completed: { - subscriber.putCompletion() - }) - disposable.set(ActionDisposable { - cancel?.dispose() - }) - } - } - - return disposable - } - } - - fileprivate func walletRestoreWords(publicKey: WalletPublicKey, decryptedSecret: Data, localPassword: Data) -> Signal<[String], WalletRestoreWordsError> { - return Signal { subscriber in - let disposable = MetaDisposable() - - self.impl.with { impl in - impl.withInstance { ton in - let cancel = ton.export(TONKey(publicKey: publicKey.rawValue, secret: decryptedSecret), localPassword: localPassword).start(next: { wordList in - guard let wordList = wordList as? [String] else { - subscriber.putError(.generic) - return - } - subscriber.putNext(wordList) - }, error: { _ in - subscriber.putError(.generic) - }, completed: { - subscriber.putCompletion() - }) - disposable.set(ActionDisposable { - cancel?.dispose() - }) - } - } - - return disposable - } - } - - fileprivate func deleteAllLocalWalletsData() -> Signal { - return Signal { subscriber in - let disposable = MetaDisposable() - - self.impl.with { impl in - impl.withInstance { ton in - let cancel = ton.deleteAllKeys().start(next: { _ in - assertionFailure() - }, error: { _ in - subscriber.putError(.generic) - }, completed: { - subscriber.putCompletion() - }) - disposable.set(ActionDisposable { - cancel?.dispose() - }) - } - } - - return disposable - } - } - fileprivate func encrypt(_ decryptedData: Data, secret: Data) -> Signal { - return Signal { subscriber in - let disposable = MetaDisposable() - - self.impl.with { impl in - impl.withInstance { ton in - subscriber.putNext(ton.encrypt(decryptedData, secret: secret)) - subscriber.putCompletion() - } - } - - return disposable - } - } - fileprivate func decrypt(_ encryptedData: Data, secret: Data) -> Signal { - return Signal { subscriber in - let disposable = MetaDisposable() - - self.impl.with { impl in - impl.withInstance { ton in - subscriber.putNext(ton.decrypt(encryptedData, secret: secret)) - subscriber.putCompletion() - } - } - - return disposable - } - } -} - -public struct WalletPublicKey: Codable, Hashable { - public var rawValue: String - - public init(rawValue: String) { - self.rawValue = rawValue - } -} - -public struct WalletInfo: PostboxCoding, Codable, Equatable { - public let publicKey: WalletPublicKey - public let encryptedSecret: TonKeychainEncryptedData - - public init(publicKey: WalletPublicKey, encryptedSecret: TonKeychainEncryptedData) { - self.publicKey = publicKey - self.encryptedSecret = encryptedSecret - } - - public init(decoder: PostboxDecoder) { - self.publicKey = WalletPublicKey(rawValue: decoder.decodeStringForKey("publicKey", orElse: "")) - if let publicKey = decoder.decodeDataForKey("encryptedSecretPublicKey"), let secret = decoder.decodeDataForKey("encryptedSecretData") { - self.encryptedSecret = TonKeychainEncryptedData(publicKey: publicKey, data: secret) - } else { - self.encryptedSecret = TonKeychainEncryptedData(publicKey: Data(), data: Data()) - } - } - - public func encode(_ encoder: PostboxEncoder) { - encoder.encodeString(self.publicKey.rawValue, forKey: "publicKey") - encoder.encodeData(self.encryptedSecret.publicKey, forKey: "encryptedSecretPublicKey") - encoder.encodeData(self.encryptedSecret.data, forKey: "encryptedSecretData") - } -} - -public struct CombinedWalletState: Codable, Equatable { - public var walletState: WalletState - public var timestamp: Int64 - public var topTransactions: [WalletTransaction] - public var pendingTransactions: [PendingWalletTransaction] -} - -public struct WalletStateRecord: PostboxCoding, Equatable { - public let info: WalletInfo - public var exportCompleted: Bool - public var state: CombinedWalletState? - - public init(info: WalletInfo, exportCompleted: Bool, state: CombinedWalletState?) { - self.info = info - self.exportCompleted = exportCompleted - self.state = state - } - - public init(decoder: PostboxDecoder) { - self.info = decoder.decodeDataForKey("info").flatMap { data in - return try? JSONDecoder().decode(WalletInfo.self, from: data) - } ?? WalletInfo(publicKey: WalletPublicKey(rawValue: ""), encryptedSecret: TonKeychainEncryptedData(publicKey: Data(), data: Data())) - self.exportCompleted = decoder.decodeInt32ForKey("exportCompleted", orElse: 0) != 0 - self.state = decoder.decodeDataForKey("state").flatMap { data in - return try? JSONDecoder().decode(CombinedWalletState.self, from: data) - } - } - - public func encode(_ encoder: PostboxEncoder) { - if let data = try? JSONEncoder().encode(self.info) { - encoder.encodeData(data, forKey: "info") - } - encoder.encodeInt32(self.exportCompleted ? 1 : 0, forKey: "exportCompleted") - if let state = self.state, let data = try? JSONEncoder().encode(state) { - encoder.encodeData(data, forKey: "state") - } else { - encoder.encodeNil(forKey: "state") - } - } -} - -public struct WalletCollection: PreferencesEntry { - public var wallets: [WalletStateRecord] - - public init(wallets: [WalletStateRecord]) { - self.wallets = wallets - } - - public init(decoder: PostboxDecoder) { - var wallets: [WalletStateRecord] = decoder.decodeObjectArrayWithDecoderForKey("wallets") - for i in (0 ..< wallets.count).reversed() { - if wallets[i].info.publicKey.rawValue.isEmpty { - wallets.remove(at: i) - } - } - self.wallets = wallets - } - - public func encode(_ encoder: PostboxEncoder) { - encoder.encodeObjectArray(self.wallets, forKey: "wallets") - } - - public func isEqual(to: PreferencesEntry) -> Bool { - guard let other = to as? WalletCollection else { - return false - } - if self.wallets != other.wallets { - return false - } - return true - } -} - -public func availableWallets(postbox: Postbox) -> Signal { - return postbox.transaction { transaction -> WalletCollection in - return (transaction.getPreferencesEntry(key: PreferencesKeys.walletCollection) as? WalletCollection) ?? WalletCollection(wallets: []) - } -} - -public enum CreateWalletError { - case generic -} - -public func tonlibEncrypt(tonInstance: TonInstance, decryptedData: Data, secret: Data) -> Signal { - return tonInstance.encrypt(decryptedData, secret: secret) -} -public func tonlibDecrypt(tonInstance: TonInstance, encryptedData: Data, secret: Data) -> Signal { - return tonInstance.decrypt(encryptedData, secret: secret) -} - -public func createWallet(postbox: Postbox, tonInstance: TonInstance, keychain: TonKeychain, localPassword: Data) -> Signal<(WalletInfo, [String]), CreateWalletError> { - return tonInstance.createWallet(keychain: keychain, localPassword: localPassword) - |> mapToSignal { walletInfo, wordList -> Signal<(WalletInfo, [String]), CreateWalletError> in - return postbox.transaction { transaction -> (WalletInfo, [String]) in - transaction.updatePreferencesEntry(key: PreferencesKeys.walletCollection, { current in - var walletCollection = (current as? WalletCollection) ?? WalletCollection(wallets: []) - walletCollection.wallets = [WalletStateRecord(info: walletInfo, exportCompleted: false, state: nil)] - return walletCollection - }) - return (walletInfo, wordList) - } - |> castError(CreateWalletError.self) - } -} - -public func confirmWalletExported(postbox: Postbox, walletInfo: WalletInfo) -> Signal { - return postbox.transaction { transaction -> Void in - transaction.updatePreferencesEntry(key: PreferencesKeys.walletCollection, { current in - var walletCollection = (current as? WalletCollection) ?? WalletCollection(wallets: []) - for i in 0 ..< walletCollection.wallets.count { - if walletCollection.wallets[i].info.publicKey == walletInfo.publicKey { - walletCollection.wallets[i].exportCompleted = true - } - } - return walletCollection - }) - } - |> ignoreValues -} - -private enum ImportWalletInternalError { - case generic -} - -public enum ImportWalletError { - case generic -} - -public func importWallet(postbox: Postbox, tonInstance: TonInstance, keychain: TonKeychain, wordList: [String], localPassword: Data) -> Signal { - return tonInstance.importWallet(keychain: keychain, wordList: wordList, localPassword: localPassword) - |> `catch` { error -> Signal in - switch error { - case .generic: - return .fail(.generic) - } - } - |> mapToSignal { walletInfo -> Signal in - return postbox.transaction { transaction -> WalletInfo in - transaction.updatePreferencesEntry(key: PreferencesKeys.walletCollection, { current in - var walletCollection = (current as? WalletCollection) ?? WalletCollection(wallets: []) - walletCollection.wallets = [WalletStateRecord(info: walletInfo, exportCompleted: true, state: nil)] - return walletCollection - }) - return walletInfo - } - |> castError(ImportWalletError.self) - } -} - -public enum DeleteAllLocalWalletsDataError { - case generic -} - -public func deleteAllLocalWalletsData(postbox: Postbox, network: Network, tonInstance: TonInstance) -> Signal { - return tonInstance.deleteAllLocalWalletsData() - |> then( - postbox.transaction { transaction -> Void in - transaction.updatePreferencesEntry(key: PreferencesKeys.walletCollection, { current in - let walletCollection = WalletCollection(wallets: []) - return walletCollection - }) - } - |> castError(DeleteAllLocalWalletsDataError.self) - |> ignoreValues - ) -} - -public enum WalletRestoreWordsError { - case generic -} - -public func walletRestoreWords(tonInstance: TonInstance, publicKey: WalletPublicKey, decryptedSecret: Data, localPassword: Data) -> Signal<[String], WalletRestoreWordsError> { - return tonInstance.walletRestoreWords(publicKey: publicKey, decryptedSecret: decryptedSecret, localPassword: localPassword) -} - -public struct WalletState: Codable, Equatable { - public let balance: Int64 - public let lastTransactionId: WalletTransactionId? - - public init(balance: Int64, lastTransactionId: WalletTransactionId?) { - self.balance = balance - self.lastTransactionId = lastTransactionId - } -} - -public func walletAddress(publicKey: WalletPublicKey, tonInstance: TonInstance) -> Signal { - return tonInstance.walletAddress(publicKey: publicKey) -} - -private enum GetWalletStateError { - case generic - case network -} - -private func getWalletState(address: String, tonInstance: TonInstance) -> Signal<(WalletState, Int64), GetWalletStateError> { - return tonInstance.getWalletState(address: address) -} - -public enum GetCombinedWalletStateError { - case generic - case network -} - -public enum CombinedWalletStateResult { - case cached(CombinedWalletState?) - case updated(CombinedWalletState) -} - -public enum CombinedWalletStateSubject { - case wallet(WalletInfo) - case address(String) -} - -public func getCombinedWalletState(postbox: Postbox, subject: CombinedWalletStateSubject, tonInstance: TonInstance, onlyCached: Bool = false) -> Signal { - switch subject { - case let .wallet(walletInfo): - return postbox.transaction { transaction -> CombinedWalletState? in - let walletCollection = (transaction.getPreferencesEntry(key: PreferencesKeys.walletCollection) as? WalletCollection) ?? WalletCollection(wallets: []) - for item in walletCollection.wallets { - if item.info.publicKey == walletInfo.publicKey { - return item.state - } - } - return nil - } - |> castError(GetCombinedWalletStateError.self) - |> mapToSignal { cachedState -> Signal in - if onlyCached { - return .single(.cached(cachedState)) - } - return .single(.cached(cachedState)) - |> then( - tonInstance.walletAddress(publicKey: walletInfo.publicKey) - |> castError(GetCombinedWalletStateError.self) - |> mapToSignal { address -> Signal in - return getWalletState(address: address, tonInstance: tonInstance) - |> retryTonRequest(isNetworkError: { error in - if case .network = error { - return true - } else { - return false - } - }) - |> mapError { error -> GetCombinedWalletStateError in - if case .network = error { - return .network - } else { - return .generic - } - } - |> mapToSignal { walletState, syncUtime -> Signal in - let topTransactions: Signal<[WalletTransaction], GetCombinedWalletStateError> - if walletState.lastTransactionId == cachedState?.walletState.lastTransactionId { - topTransactions = .single(cachedState?.topTransactions ?? []) - } else { - topTransactions = getWalletTransactions(address: address, previousId: nil, tonInstance: tonInstance) - |> mapError { error -> GetCombinedWalletStateError in - if case .network = error { - return .network - } else { - return .generic - } - } - } - return topTransactions - |> mapToSignal { topTransactions -> Signal in - let lastTransactionTimestamp = topTransactions.last?.timestamp - var listTransactionBodyHashes = Set() - for transaction in topTransactions { - if let message = transaction.inMessage { - listTransactionBodyHashes.insert(message.bodyHash) - } - for message in transaction.outMessages { - listTransactionBodyHashes.insert(message.bodyHash) - } - } - let pendingTransactions = (cachedState?.pendingTransactions ?? []).filter { transaction in - if transaction.validUntilTimestamp <= syncUtime { - return false - } else if let lastTransactionTimestamp = lastTransactionTimestamp, transaction.validUntilTimestamp <= lastTransactionTimestamp { - return false - } else { - if listTransactionBodyHashes.contains(transaction.bodyHash) { - return false - } - return true - } - } - let combinedState = CombinedWalletState(walletState: walletState, timestamp: syncUtime, topTransactions: topTransactions, pendingTransactions: pendingTransactions) - return postbox.transaction { transaction -> CombinedWalletStateResult in - transaction.updatePreferencesEntry(key: PreferencesKeys.walletCollection, { current in - var walletCollection = (current as? WalletCollection) ?? WalletCollection(wallets: []) - for i in 0 ..< walletCollection.wallets.count { - if walletCollection.wallets[i].info.publicKey == walletInfo.publicKey { - walletCollection.wallets[i].state = combinedState - } - } - return walletCollection - }) - return .updated(combinedState) - } - |> castError(GetCombinedWalletStateError.self) - } - } - } - ) - } - case let .address(address): - let updated = getWalletState(address: address, tonInstance: tonInstance) - |> mapError { _ -> GetCombinedWalletStateError in - return .generic - } - |> mapToSignal { walletState, syncUtime -> Signal in - let topTransactions: Signal<[WalletTransaction], GetCombinedWalletStateError> - - topTransactions = getWalletTransactions(address: address, previousId: nil, tonInstance: tonInstance) - |> mapError { _ -> GetCombinedWalletStateError in - return .generic - } - return topTransactions - |> mapToSignal { topTransactions -> Signal in - let combinedState = CombinedWalletState(walletState: walletState, timestamp: syncUtime, topTransactions: topTransactions, pendingTransactions: []) - return .single(.updated(combinedState)) - } - } - return .single(.cached(nil)) - |> then(updated) - } -} - -public enum SendGramsFromWalletError { - case generic - case secretDecryptionFailed - case invalidAddress - case destinationIsNotInitialized - case messageTooLong - case notEnoughFunds - case network -} - -public func sendGramsFromWallet(postbox: Postbox, network: Network, tonInstance: TonInstance, walletInfo: WalletInfo, decryptedSecret: Data, localPassword: Data, toAddress: String, amount: Int64, textMessage: Data, forceIfDestinationNotInitialized: Bool, timeout: Int32, randomId: Int64) -> Signal<[PendingWalletTransaction], SendGramsFromWalletError> { - return walletAddress(publicKey: walletInfo.publicKey, tonInstance: tonInstance) - |> castError(SendGramsFromWalletError.self) - |> mapToSignal { fromAddress -> Signal<[PendingWalletTransaction], SendGramsFromWalletError> in - return tonInstance.sendGramsFromWallet(decryptedSecret: decryptedSecret, localPassword: localPassword, walletInfo: walletInfo, fromAddress: fromAddress, toAddress: toAddress, amount: amount, textMessage: textMessage, forceIfDestinationNotInitialized: forceIfDestinationNotInitialized, timeout: timeout, randomId: randomId) - |> mapToSignal { result -> Signal<[PendingWalletTransaction], SendGramsFromWalletError> in - return postbox.transaction { transaction -> [PendingWalletTransaction] in - var updatedPendingTransactions: [PendingWalletTransaction] = [] - transaction.updatePreferencesEntry(key: PreferencesKeys.walletCollection, { current in - var walletCollection = (current as? WalletCollection) ?? WalletCollection(wallets: []) - for i in 0 ..< walletCollection.wallets.count { - if walletCollection.wallets[i].info.publicKey == walletInfo.publicKey { - if var state = walletCollection.wallets[i].state { - state.pendingTransactions.insert(result, at: 0) - walletCollection.wallets[i].state = state - updatedPendingTransactions = state.pendingTransactions - } - } - } - return walletCollection - }) - return updatedPendingTransactions - } - |> castError(SendGramsFromWalletError.self) - } - } -} - -public struct WalletTransactionId: Codable, Hashable { - public var lt: Int64 - public var transactionHash: Data -} - -private extension WalletTransactionId { - init(tonTransactionId: TONTransactionId) { - self.lt = tonTransactionId.lt - self.transactionHash = tonTransactionId.transactionHash - } -} - -public final class WalletTransactionMessage: Codable, Equatable { - public let value: Int64 - public let source: String - public let destination: String - public let textMessage: String - public let bodyHash: Data - - init(value: Int64, source: String, destination: String, textMessage: String, bodyHash: Data) { - self.value = value - self.source = source - self.destination = destination - self.textMessage = textMessage - self.bodyHash = bodyHash - } - - public static func ==(lhs: WalletTransactionMessage, rhs: WalletTransactionMessage) -> Bool { - if lhs.value != rhs.value { - return false - } - if lhs.source != rhs.source { - return false - } - if lhs.destination != rhs.destination { - return false - } - if lhs.textMessage != rhs.textMessage { - return false - } - if lhs.bodyHash != rhs.bodyHash { - return false - } - return true - } -} - -private extension WalletTransactionMessage { - convenience init(tonTransactionMessage: TONTransactionMessage) { - self.init(value: tonTransactionMessage.value, source: tonTransactionMessage.source, destination: tonTransactionMessage.destination, textMessage: tonTransactionMessage.textMessage, bodyHash: tonTransactionMessage.bodyHash) - } -} - -public final class PendingWalletTransaction: Codable, Equatable { - public let timestamp: Int64 - public let validUntilTimestamp: Int64 - public let bodyHash: Data - public let address: String - public let value: Int64 - public let comment: Data - - public init(timestamp: Int64, validUntilTimestamp: Int64, bodyHash: Data, address: String, value: Int64, comment: Data) { - self.timestamp = timestamp - self.validUntilTimestamp = validUntilTimestamp - self.bodyHash = bodyHash - self.address = address - self.value = value - self.comment = comment - } - - public static func ==(lhs: PendingWalletTransaction, rhs: PendingWalletTransaction) -> Bool { - if lhs.timestamp != rhs.timestamp { - return false - } - if lhs.validUntilTimestamp != rhs.validUntilTimestamp { - return false - } - if lhs.bodyHash != rhs.bodyHash { - return false - } - if lhs.value != rhs.value { - return false - } - if lhs.comment != rhs.comment { - return false - } - return true - } -} - -public final class WalletTransaction: Codable, Equatable { - public let data: Data - public let transactionId: WalletTransactionId - public let timestamp: Int64 - public let storageFee: Int64 - public let otherFee: Int64 - public let inMessage: WalletTransactionMessage? - public let outMessages: [WalletTransactionMessage] - - public var transferredValueWithoutFees: Int64 { - var value: Int64 = 0 - if let inMessage = self.inMessage { - value += inMessage.value - } - for message in self.outMessages { - value -= message.value - } - return value - } - - init(data: Data, transactionId: WalletTransactionId, timestamp: Int64, storageFee: Int64, otherFee: Int64, inMessage: WalletTransactionMessage?, outMessages: [WalletTransactionMessage]) { - self.data = data - self.transactionId = transactionId - self.timestamp = timestamp - self.storageFee = storageFee - self.otherFee = otherFee - self.inMessage = inMessage - self.outMessages = outMessages - } - - public static func ==(lhs: WalletTransaction, rhs: WalletTransaction) -> Bool { - if lhs.data != rhs.data { - return false - } - if lhs.transactionId != rhs.transactionId { - return false - } - if lhs.timestamp != rhs.timestamp { - return false - } - if lhs.storageFee != rhs.storageFee { - return false - } - if lhs.otherFee != rhs.otherFee { - return false - } - if lhs.inMessage != rhs.inMessage { - return false - } - if lhs.outMessages != rhs.outMessages { - return false - } - return true - } -} - -private extension WalletTransaction { - convenience init(tonTransaction: TONTransaction) { - self.init(data: tonTransaction.data, transactionId: WalletTransactionId(tonTransactionId: tonTransaction.transactionId), timestamp: tonTransaction.timestamp, storageFee: tonTransaction.storageFee, otherFee: tonTransaction.otherFee, inMessage: tonTransaction.inMessage.flatMap(WalletTransactionMessage.init(tonTransactionMessage:)), outMessages: tonTransaction.outMessages.map(WalletTransactionMessage.init(tonTransactionMessage:))) - } -} - -public enum GetWalletTransactionsError { - case generic - case network -} - -public func getWalletTransactions(address: String, previousId: WalletTransactionId?, tonInstance: TonInstance) -> Signal<[WalletTransaction], GetWalletTransactionsError> { - return getWalletTransactionsOnce(address: address, previousId: previousId, tonInstance: tonInstance) - |> mapToSignal { transactions in - guard let lastTransaction = transactions.last, transactions.count >= 2 else { - return .single(transactions) - } - return getWalletTransactionsOnce(address: address, previousId: lastTransaction.transactionId, tonInstance: tonInstance) - |> map { additionalTransactions in - var result = transactions - var existingIds = Set(result.map { $0.transactionId }) - for transaction in additionalTransactions { - if !existingIds.contains(transaction.transactionId) { - existingIds.insert(transaction.transactionId) - result.append(transaction) - } - } - return result - } - } -} - -private func retryTonRequest(isNetworkError: @escaping (E) -> Bool) -> (Signal) -> Signal { - return { signal in - return signal - |> retry(retryOnError: isNetworkError, delayIncrement: 0.2, maxDelay: 5.0, maxRetries: 3, onQueue: Queue.concurrentDefaultQueue()) - } -} - -private enum WalletLastTransactionIdError { - case generic - case network -} - -private func getWalletTransactionsOnce(address: String, previousId: WalletTransactionId?, tonInstance: TonInstance) -> Signal<[WalletTransaction], GetWalletTransactionsError> { - let previousIdValue: Signal - if let previousId = previousId { - previousIdValue = .single(previousId) - } else { - previousIdValue = tonInstance.walletLastTransactionId(address: address) - |> retryTonRequest(isNetworkError: { error in - if case .network = error { - return true - } else { - return false - } - }) - |> mapError { error -> GetWalletTransactionsError in - if case .network = error { - return .network - } else { - return .generic - } - } - } - return previousIdValue - |> mapToSignal { previousId in - if let previousId = previousId { - return tonInstance.getWalletTransactions(address: address, previousId: previousId) - |> retryTonRequest(isNetworkError: { error in - if case .network = error { - return true - } else { - return false - } - }) - } else { - return .single([]) - } - } -} - public enum GetServerWalletSaltError { case generic } @@ -1098,3 +27,75 @@ public func getServerWalletSalt(network: Network) -> Signal Signal { + return network.request(Api.functions.wallet.sendLiteRequest(body: Buffer(data: data))) + |> mapError { error -> WalletProxyRequestError in + return .generic(error.errorCode, error.errorDescription) + } + |> map { result -> Data in + switch result { + case let .liteResponse(response): + return response.makeData() + } + } +} + +public struct WalletCollection: PreferencesEntry { + public var wallets: [WalletCollectionItem] + + public init(wallets: [WalletCollectionItem]) { + self.wallets = wallets + } + + public init(decoder: PostboxDecoder) { + self.wallets = decoder.decodeObjectArrayWithDecoderForKey("wallets") + } + + public func encode(_ encoder: PostboxEncoder) { + encoder.encodeObjectArray(self.wallets, forKey: "wallets") + } + + public func isEqual(to: PreferencesEntry) -> Bool { + guard let other = to as? WalletCollection else { + return false + } + if self.wallets != other.wallets { + return false + } + return true + } +} diff --git a/submodules/TelegramPermissionsUI/BUCK b/submodules/TelegramPermissionsUI/BUCK index b7dc3c5751..c966bce116 100644 --- a/submodules/TelegramPermissionsUI/BUCK +++ b/submodules/TelegramPermissionsUI/BUCK @@ -14,11 +14,13 @@ static_library( "//submodules/TelegramPresentationData:TelegramPresentationData", "//submodules/AccountContext:AccountContext", "//submodules/TextFormat:TextFormat", + "//submodules/Markdown:Markdown", "//submodules/TelegramPermissions:TelegramPermissions", "//submodules/DeviceAccess:DeviceAccess", "//submodules/PeersNearbyIconNode:PeersNearbyIconNode", "//submodules/SolidRoundedButtonNode:SolidRoundedButtonNode", "//submodules/AppBundle:AppBundle", + "//submodules/PresentationDataUtils:PresentationDataUtils", ], frameworks = [ "$SDKROOT/System/Library/Frameworks/Foundation.framework", diff --git a/submodules/TelegramPermissionsUI/Sources/PermissionContentNode.swift b/submodules/TelegramPermissionsUI/Sources/PermissionContentNode.swift index 7741917aea..dc0c5a123e 100644 --- a/submodules/TelegramPermissionsUI/Sources/PermissionContentNode.swift +++ b/submodules/TelegramPermissionsUI/Sources/PermissionContentNode.swift @@ -7,6 +7,8 @@ import TextFormat import TelegramPermissions import PeersNearbyIconNode import SolidRoundedButtonNode +import PresentationDataUtils +import Markdown public enum PermissionContentIcon { case image(UIImage?) diff --git a/submodules/TelegramPresentationData/Sources/PresentationStrings.swift b/submodules/TelegramPresentationData/Sources/PresentationStrings.swift index 6652bb17a8..5d1721dfd9 100644 --- a/submodules/TelegramPresentationData/Sources/PresentationStrings.swift +++ b/submodules/TelegramPresentationData/Sources/PresentationStrings.swift @@ -4446,566 +4446,566 @@ public final class PresentationStrings: Equatable { public var Channel_Setup_TypePublicHelp: String { return self._s[3909]! } public var Passport_Identity_EditInternalPassport: String { return self._s[3910]! } public var PhotoEditor_Skip: String { return self._s[3911]! } - public func Watch_LastSeen_HoursAgo(_ value: Int32) -> String { + public func StickerPack_RemoveMaskCount(_ 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 Passport_Scans(_ value: Int32) -> String { + public func MuteExpires_Hours(_ 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 PUSH_CHAT_MESSAGE_ROUNDS(_ selector: Int32, _ _2: String, _ _1: String, _ _3: Int32) -> String { - let form = getPluralizationForm(self.lc, selector) - return String(format: self._ps[2 * 6 + Int(form.rawValue)]!, _2, _1, _3) - } public func Conversation_LiveLocationMembersCount(_ 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 PasscodeSettings_FailedAttempts(_ 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 Wallpaper_DeleteConfirmation(_ 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 MessageTimer_ShortDays(_ 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 AttachmentMenu_SendVideo(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[7 * 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[8 * 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[9 * 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[10 * 6 + Int(form.rawValue)]!, _1, _2) + return String(format: self._ps[2 * 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[11 * 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[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 VoiceOver_Chat_PollOptionCount(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[14 * 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[15 * 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[16 * 6 + Int(form.rawValue)]!, _2, _1, _3) - } - public func ServiceMessage_GameScoreSimple(_ 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 ForwardedFiles(_ 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 ForwardedAuthorsOthers(_ selector: Int32, _ _0: String, _ _1: String) -> String { - let form = getPluralizationForm(self.lc, selector) - return String(format: self._ps[19 * 6 + Int(form.rawValue)]!, _0, _1) - } - public func ChatList_DeleteConfirmation(_ 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 Watch_LastSeen_MinutesAgo(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[21 * 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[22 * 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[23 * 6 + Int(form.rawValue)]!, _1, _2) - } - public func MessageTimer_Hours(_ 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 MessagePoll_VotedCount(_ 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 VoiceOver_Chat_ContactPhoneNumberCount(_ 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 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 SharedMedia_File(_ 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 PUSH_CHANNEL_MESSAGE_FWDS(_ selector: Int32, _ _1: String, _ _2: Int32) -> String { - let form = getPluralizationForm(self.lc, selector) - return String(format: self._ps[29 * 6 + Int(form.rawValue)]!, _1, _2) - } - public func ForwardedContacts(_ 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 SharedMedia_Photo(_ 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 PrivacyLastSeenSettings_AddUsers(_ 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 LiveLocationUpdated_MinutesAgo(_ 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 ForwardedGifs(_ 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 Map_ETAMinutes(_ 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 MessageTimer_ShortWeeks(_ 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 Theme_UsersCount(_ 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 PUSH_MESSAGE_PHOTOS(_ selector: Int32, _ _1: String, _ _2: Int32) -> String { - let form = getPluralizationForm(self.lc, selector) - return String(format: self._ps[38 * 6 + Int(form.rawValue)]!, _1, _2) - } - public func PUSH_CHANNEL_MESSAGE_ROUNDS(_ selector: Int32, _ _1: String, _ _2: Int32) -> String { - let form = getPluralizationForm(self.lc, selector) - return String(format: self._ps[39 * 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[40 * 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[41 * 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[42 * 6 + Int(form.rawValue)]!, _1, _2) - } - public func InviteText_ContactsCountText(_ 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 MessageTimer_ShortSeconds(_ 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 MuteExpires_Minutes(_ 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 LastSeen_HoursAgo(_ 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 Forward_ConfirmMultipleFiles(_ 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 Conversation_StatusSubscribers(_ 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 Notifications_ExceptionMuteExpires_Minutes(_ 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 Media_ShareVideo(_ 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 MessageTimer_ShortHours(_ 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 Conversation_SelectedMessages(_ 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 MessageTimer_Days(_ 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 StickerPack_RemoveStickerCount(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[54 * 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[55 * 6 + Int(form.rawValue)]!, stringValue) - } - public func QuickSend_Photos(_ 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 MuteFor_Hours(_ 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 UserCount(_ 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 ForwardedVideos(_ 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 SharedMedia_Generic(_ 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 MessageTimer_ShortMinutes(_ 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 Wallet_Updated_HoursAgo(_ 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 Map_ETAHours(_ 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 StickerPack_AddMaskCount(_ 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 ForwardedMessages(_ 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 Notifications_ExceptionMuteExpires_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 MuteExpires_Hours(_ 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 Call_Seconds(_ 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 MessageTimer_Minutes(_ 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 SharedMedia_DeleteItemsConfirmation(_ 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 ServiceMessage_GameScoreSelfExtended(_ 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 PUSH_CHAT_MESSAGE_VIDEOS(_ selector: Int32, _ _2: String, _ _1: String, _ _3: Int32) -> String { - let form = getPluralizationForm(self.lc, selector) - return String(format: self._ps[72 * 6 + Int(form.rawValue)]!, _2, _1, _3) - } - public func Media_SharePhoto(_ 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 AttachmentMenu_SendPhoto(_ 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 PUSH_CHANNEL_MESSAGE_PHOTOS(_ selector: Int32, _ _1: String, _ _2: Int32) -> String { - let form = getPluralizationForm(self.lc, selector) - return String(format: self._ps[75 * 6 + Int(form.rawValue)]!, _1, _2) - } - 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[76 * 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[77 * 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[78 * 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[79 * 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[80 * 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[81 * 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[82 * 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[83 * 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[84 * 6 + Int(form.rawValue)]!, stringValue) - } - public func SharedMedia_Video(_ 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 ForwardedAudios(_ 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) + return String(format: self._ps[3 * 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[87 * 6 + Int(form.rawValue)]!, stringValue) + return String(format: self._ps[4 * 6 + Int(form.rawValue)]!, stringValue) } - public func CreatePoll_AddMoreOptions(_ value: Int32) -> String { + public func ServiceMessage_GameScoreSelfExtended(_ 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) + return String(format: self._ps[5 * 6 + Int(form.rawValue)]!, stringValue) } - public func PUSH_CHAT_MESSAGE_FWDS(_ selector: Int32, _ _2: String, _ _1: String, _ _3: Int32) -> String { + public func Forward_ConfirmMultipleFiles(_ 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 ForwardedAudios(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[7 * 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[89 * 6 + Int(form.rawValue)]!, _2, _1, _3) + return String(format: self._ps[8 * 6 + Int(form.rawValue)]!, _1, _2) } - public func AttachmentMenu_SendGif(_ value: Int32) -> String { + public func MessageTimer_Minutes(_ 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) + return String(format: self._ps[9 * 6 + Int(form.rawValue)]!, stringValue) } - public func VoiceOver_Chat_ContactEmailCount(_ value: Int32) -> String { + public func Call_ShortSeconds(_ 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) + return String(format: self._ps[10 * 6 + Int(form.rawValue)]!, stringValue) } - public func Watch_UserInfo_Mute(_ value: Int32) -> String { + public func MessageTimer_ShortHours(_ 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) + return String(format: self._ps[11 * 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[93 * 6 + Int(form.rawValue)]!, _2, _1, _3) - } - public func MessageTimer_Weeks(_ value: Int32) -> String { + 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[94 * 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[95 * 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[96 * 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[97 * 6 + Int(form.rawValue)]!, stringValue) + return String(format: self._ps[12 * 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[98 * 6 + Int(form.rawValue)]!, stringValue) + return String(format: self._ps[13 * 6 + Int(form.rawValue)]!, stringValue) } - public func StickerPack_RemoveMaskCount(_ value: Int32) -> String { + 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[99 * 6 + Int(form.rawValue)]!, stringValue) + return String(format: self._ps[14 * 6 + Int(form.rawValue)]!, stringValue) } - public func MuteFor_Days(_ value: Int32) -> String { + public func StickerPack_AddMaskCount(_ 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) + return String(format: self._ps[15 * 6 + Int(form.rawValue)]!, stringValue) } - public func MessageTimer_Years(_ value: Int32) -> String { + public func ForwardedVideoMessages(_ 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 Call_Minutes(_ 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_MESSAGES(_ 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 PUSH_MESSAGE_FWDS(_ selector: Int32, _ _1: String, _ _2: Int32) -> String { - let form = getPluralizationForm(self.lc, selector) - return String(format: self._ps[104 * 6 + Int(form.rawValue)]!, _1, _2) - } - public func ForwardedLocations(_ 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 ForwardedPolls(_ 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 Chat_DeleteMessagesConfirmation(_ 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) + return String(format: self._ps[16 * 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[108 * 6 + Int(form.rawValue)]!, stringValue) + return String(format: self._ps[17 * 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[18 * 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[19 * 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[20 * 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[21 * 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[22 * 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[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 PUSH_CHANNEL_MESSAGE_FWDS(_ 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 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 MuteFor_Hours(_ 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 PUSH_MESSAGE_PHOTOS(_ selector: Int32, _ _1: String, _ _2: Int32) -> String { + let form = getPluralizationForm(self.lc, selector) + return String(format: self._ps[28 * 6 + Int(form.rawValue)]!, _1, _2) + } + public func PUSH_CHANNEL_MESSAGES(_ selector: Int32, _ _1: String, _ _2: Int32) -> String { + let form = getPluralizationForm(self.lc, selector) + return String(format: self._ps[29 * 6 + Int(form.rawValue)]!, _1, _2) + } + public func MuteExpires_Days(_ 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 MuteExpires_Minutes(_ 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 MessageTimer_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 UserCount(_ 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 PUSH_MESSAGE_VIDEOS(_ selector: Int32, _ _1: String, _ _2: Int32) -> String { + let form = getPluralizationForm(self.lc, selector) + return String(format: self._ps[34 * 6 + Int(form.rawValue)]!, _1, _2) + } + public func MessageTimer_ShortDays(_ 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 PUSH_CHANNEL_MESSAGE_PHOTOS(_ selector: Int32, _ _1: String, _ _2: Int32) -> String { + let form = getPluralizationForm(self.lc, selector) + return String(format: self._ps[36 * 6 + Int(form.rawValue)]!, _1, _2) + } + public func ForwardedLocations(_ 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 Notification_GameScoreSelfSimple(_ 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 Call_Minutes(_ 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 VoiceOver_Chat_PollOptionCount(_ 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 QuickSend_Photos(_ 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 ForwardedAuthorsOthers(_ selector: Int32, _ _0: String, _ _1: String) -> String { + let form = getPluralizationForm(self.lc, selector) + return String(format: self._ps[42 * 6 + Int(form.rawValue)]!, _0, _1) + } + public func SharedMedia_Generic(_ 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 StickerPack_StickerCount(_ 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 Conversation_StatusMembers(_ 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 Watch_LastSeen_HoursAgo(_ 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 Media_SharePhoto(_ 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 AttachmentMenu_SendVideo(_ 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 Chat_DeleteMessagesConfirmation(_ 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 ServiceMessage_GameScoreExtended(_ 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) + return String(format: self._ps[50 * 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[51 * 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[52 * 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[53 * 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[54 * 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[55 * 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[56 * 6 + Int(form.rawValue)]!, stringValue) + } + public func MessageTimer_Days(_ 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 ForwardedContacts(_ 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 Watch_UserInfo_Mute(_ 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 Notifications_ExceptionMuteExpires_Days(_ 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 Passport_Scans(_ 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 Conversation_SelectedMessages(_ 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 Invitation_Members(_ 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 Call_ShortMinutes(_ 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 ForwardedPhotos(_ 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 Notifications_Exceptions(_ 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[66 * 6 + Int(form.rawValue)]!, stringValue) } - public func Notification_GameScoreSimple(_ value: Int32) -> String { + public func Map_ETAMinutes(_ 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[67 * 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[68 * 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[69 * 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[70 * 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[71 * 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[72 * 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[73 * 6 + Int(form.rawValue)]!, _2, _1, _3) + } + public func StickerPack_AddStickerCount(_ 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 LastSeen_HoursAgo(_ 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 InviteText_ContactsCountText(_ 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 PUSH_CHAT_MESSAGE_PHOTOS(_ selector: Int32, _ _2: String, _ _1: String, _ _3: Int32) -> String { + let form = getPluralizationForm(self.lc, selector) + return String(format: self._ps[77 * 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[78 * 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[79 * 6 + Int(form.rawValue)]!, _2, _1, _3) + } + public func PUSH_MESSAGE_FWDS(_ selector: Int32, _ _1: String, _ _2: Int32) -> String { + let form = getPluralizationForm(self.lc, selector) + return String(format: self._ps[80 * 6 + Int(form.rawValue)]!, _1, _2) + } + public func PasscodeSettings_FailedAttempts(_ 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 Media_ShareItem(_ 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[82 * 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[83 * 6 + Int(form.rawValue)]!, stringValue) + } + public func MessagePoll_VotedCount(_ 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 ServiceMessage_GameScoreSimple(_ 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 Contacts_ImportersCount(_ 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 SharedMedia_Link(_ 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 ServiceMessage_GameScoreSelfSimple(_ 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 MessageTimer_ShortWeeks(_ 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 Notification_GameScoreExtended(_ 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 ForwardedFiles(_ 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 ForwardedVideos(_ 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 Notification_GameScoreSimple(_ 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 Call_Seconds(_ 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 PUSH_CHAT_MESSAGE_FWDS(_ selector: Int32, _ _2: String, _ _1: String, _ _3: Int32) -> String { + let form = getPluralizationForm(self.lc, selector) + return String(format: self._ps[95 * 6 + Int(form.rawValue)]!, _2, _1, _3) + } + public func LastSeen_MinutesAgo(_ 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 SharedMedia_File(_ 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 SharedMedia_DeleteItemsConfirmation(_ 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 CreatePoll_AddMoreOptions(_ 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 PUSH_CHAT_MESSAGES(_ selector: Int32, _ _2: String, _ _1: String, _ _3: Int32) -> String { + let form = getPluralizationForm(self.lc, selector) + return String(format: self._ps[100 * 6 + Int(form.rawValue)]!, _2, _1, _3) + } + public func ForwardedStickers(_ 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 Map_ETAHours(_ 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 Wallet_Updated_HoursAgo(_ 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 AttachmentMenu_SendGif(_ 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 PUSH_CHANNEL_MESSAGE_ROUNDS(_ selector: Int32, _ _1: String, _ _2: Int32) -> String { + let form = getPluralizationForm(self.lc, selector) + return String(format: self._ps[105 * 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[106 * 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[107 * 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[108 * 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[109 * 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[110 * 6 + Int(form.rawValue)]!, _1, _2) + } + public func PUSH_MESSAGE_ROUNDS(_ selector: Int32, _ _1: String, _ _2: Int32) -> String { + let form = getPluralizationForm(self.lc, selector) + return String(format: self._ps[111 * 6 + Int(form.rawValue)]!, _1, _2) + } + public func Wallpaper_DeleteConfirmation(_ 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) + } + public func DialogList_LiveLocationChatsCount(_ 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 PUSH_CHANNEL_MESSAGE_VIDEOS(_ selector: Int32, _ _1: String, _ _2: Int32) -> String { - let form = getPluralizationForm(self.lc, selector) - return String(format: self._ps[114 * 6 + Int(form.rawValue)]!, _1, _2) + public func Conversation_StatusSubscribers(_ 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 MuteExpires_Days(_ value: Int32) -> String { + public func AttachmentMenu_SendPhoto(_ 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) diff --git a/submodules/TelegramStringFormatting/BUCK b/submodules/TelegramStringFormatting/BUCK index 432cd2f422..a0437f7beb 100644 --- a/submodules/TelegramStringFormatting/BUCK +++ b/submodules/TelegramStringFormatting/BUCK @@ -12,6 +12,7 @@ static_library( "//submodules/PlatformRestrictionMatching:PlatformRestrictionMatching", "//submodules/LocalizedPeerData:LocalizedPeerData", "//submodules/TextFormat:TextFormat", + "//submodules/Markdown:Markdown", "//submodules/TelegramUIPreferences:TelegramUIPreferences", "//submodules/TelegramPresentationData:TelegramPresentationData", "//submodules/AppBundle:AppBundle", diff --git a/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift b/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift index 3fc3b9223d..21ab40a1be 100644 --- a/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift +++ b/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift @@ -6,6 +6,7 @@ import TelegramUIPreferences import TextFormat import LocalizedPeerData import Display +import Markdown private let titleFont = Font.regular(13.0) private let titleBoldFont = Font.bold(13.0) diff --git a/submodules/TelegramUI/BUCK b/submodules/TelegramUI/BUCK index ac79a13db7..80ec5806b9 100644 --- a/submodules/TelegramUI/BUCK +++ b/submodules/TelegramUI/BUCK @@ -83,6 +83,8 @@ framework( "//submodules/HorizontalPeerItem:HorizontalPeerItem", "//submodules/CheckNode:CheckNode", "//submodules/AnimationUI:AnimationUI", + "//submodules/AnimatedStickerNode:AnimatedStickerNode", + "//submodules/TelegramAnimatedStickerNode:TelegramAnimatedStickerNode", "//submodules/ActionSheetPeerItem:ActionSheetPeerItem", "//submodules/AccountContext:AccountContext", "//submodules/ComposePollUI:ComposePollUI", @@ -184,6 +186,8 @@ framework( "//submodules/SegmentedControlNode:SegmentedControlNode", "//submodules/AppBundle:AppBundle", "//submodules/WalletUI:WalletUI", + "//submodules/WalletCore:WalletCore", + "//submodules/Markdown:Markdown", ], frameworks = [ "$SDKROOT/System/Library/Frameworks/Foundation.framework", diff --git a/submodules/TelegramUI/TelegramUI/AccountContext.swift b/submodules/TelegramUI/TelegramUI/AccountContext.swift index ba5de0fe18..942b5d8dc6 100644 --- a/submodules/TelegramUI/TelegramUI/AccountContext.swift +++ b/submodules/TelegramUI/TelegramUI/AccountContext.swift @@ -9,6 +9,7 @@ import TelegramPresentationData import AccountContext import LiveLocationManager import TemporaryCachedPeerDataManager +import WalletCore private final class DeviceSpecificContactImportContext { let disposable = MetaDisposable() @@ -131,6 +132,13 @@ public final class AccountContextImpl: AccountContext { private let deviceSpecificContactImportContexts: QueueLocalObject private var managedAppSpecificContactsDisposable: Disposable? + public var hasWallets: Signal { + return WalletStorageInterfaceImpl(postbox: self.account.postbox).getWalletRecords() + |> map { records in + return !records.isEmpty + } + } + public init(sharedContext: SharedAccountContextImpl, account: Account, tonContext: StoredTonContext?, limitsConfiguration: LimitsConfiguration) { self.sharedContextImpl = sharedContext self.account = account diff --git a/submodules/TelegramUI/TelegramUI/AppDelegate.swift b/submodules/TelegramUI/TelegramUI/AppDelegate.swift index fcd70db83f..ef7508f77d 100644 --- a/submodules/TelegramUI/TelegramUI/AppDelegate.swift +++ b/submodules/TelegramUI/TelegramUI/AppDelegate.swift @@ -26,6 +26,8 @@ import SettingsUI import AppBundle import WalletUI import UrlHandling +import WalletUrl +import WalletCore private let handleVoipNotifications = false diff --git a/submodules/TelegramUI/TelegramUI/AuthorizationSequenceSignUpControllerNode.swift b/submodules/TelegramUI/TelegramUI/AuthorizationSequenceSignUpControllerNode.swift index e004a71736..ec90b2aa2d 100644 --- a/submodules/TelegramUI/TelegramUI/AuthorizationSequenceSignUpControllerNode.swift +++ b/submodules/TelegramUI/TelegramUI/AuthorizationSequenceSignUpControllerNode.swift @@ -4,6 +4,7 @@ import AsyncDisplayKit import Display import TelegramPresentationData import TextFormat +import Markdown private func roundCorners(diameter: CGFloat) -> UIImage { UIGraphicsBeginImageContextWithOptions(CGSize(width: diameter, height: diameter), false, 0.0) diff --git a/submodules/TelegramUI/TelegramUI/ChatController.swift b/submodules/TelegramUI/TelegramUI/ChatController.swift index f4d1ace892..1e36e54e2a 100644 --- a/submodules/TelegramUI/TelegramUI/ChatController.swift +++ b/submodules/TelegramUI/TelegramUI/ChatController.swift @@ -48,6 +48,7 @@ import ReactionSelectionNode import MessageReactionListUI import AppBundle import WalletUI +import WalletUrl public enum ChatControllerPeekActions { case standard diff --git a/submodules/TelegramUI/TelegramUI/ChatMediaInputStickerGridItem.swift b/submodules/TelegramUI/TelegramUI/ChatMediaInputStickerGridItem.swift index 2ef02fbf11..d6fafc4731 100644 --- a/submodules/TelegramUI/TelegramUI/ChatMediaInputStickerGridItem.swift +++ b/submodules/TelegramUI/TelegramUI/ChatMediaInputStickerGridItem.swift @@ -8,7 +8,8 @@ import Postbox import TelegramPresentationData import StickerResources import AccountContext -import AnimationUI +import AnimatedStickerNode +import TelegramAnimatedStickerNode enum ChatMediaInputStickerGridSectionAccessory { case none @@ -299,7 +300,7 @@ final class ChatMediaInputStickerGridItemNode: GridItemNode { self.didSetUpAnimationNode = true let dimensions = item.stickerItem.file.dimensions ?? CGSize(width: 512.0, height: 512.0) let fittedDimensions = dimensions.aspectFitted(CGSize(width: 160.0, height: 160.0)) - self.animationNode?.setup(resource: .resource(item.account, item.stickerItem.file.resource), width: Int(fittedDimensions.width), height: Int(fittedDimensions.height), mode: .cached) + self.animationNode?.setup(source: AnimatedStickerResourceSource(account: item.account, resource: item.stickerItem.file.resource), width: Int(fittedDimensions.width), height: Int(fittedDimensions.height), mode: .cached) } } } diff --git a/submodules/TelegramUI/TelegramUI/ChatMediaInputStickerPackItem.swift b/submodules/TelegramUI/TelegramUI/ChatMediaInputStickerPackItem.swift index 43d945b08d..406910bbbf 100644 --- a/submodules/TelegramUI/TelegramUI/ChatMediaInputStickerPackItem.swift +++ b/submodules/TelegramUI/TelegramUI/ChatMediaInputStickerPackItem.swift @@ -7,8 +7,9 @@ import SwiftSignalKit import Postbox import TelegramPresentationData import StickerResources -import AnimationUI import ItemListStickerPackItem +import AnimatedStickerNode +import TelegramAnimatedStickerNode final class ChatMediaInputStickerPackItem: ListViewItem { let account: Account @@ -177,7 +178,7 @@ final class ChatMediaInputStickerPackItemNode: ListViewItemNode { self.animatedStickerNode = animatedStickerNode animatedStickerNode.transform = CATransform3DMakeRotation(CGFloat.pi / 2.0, 0.0, 0.0, 1.0) self.addSubnode(animatedStickerNode) - animatedStickerNode.setup(resource: .resource(account, resource), width: 80, height: 80, mode: .cached) + animatedStickerNode.setup(source: AnimatedStickerResourceSource(account: account, resource: resource), width: 80, height: 80, mode: .cached) } animatedStickerNode.visibility = self.visibilityStatus && loopAnimatedStickers if let animatedStickerNode = self.animatedStickerNode { diff --git a/submodules/TelegramUI/TelegramUI/ChatMessageActionUrlAuthController.swift b/submodules/TelegramUI/TelegramUI/ChatMessageActionUrlAuthController.swift index 9fcf97cfdb..ce9e5af3ee 100644 --- a/submodules/TelegramUI/TelegramUI/ChatMessageActionUrlAuthController.swift +++ b/submodules/TelegramUI/TelegramUI/ChatMessageActionUrlAuthController.swift @@ -9,6 +9,7 @@ import TelegramPresentationData import CheckNode import TextFormat import AccountContext +import Markdown private let textFont = Font.regular(13.0) private let boldTextFont = Font.semibold(13.0) diff --git a/submodules/TelegramUI/TelegramUI/ChatMessageAnimatedStickerItemNode.swift b/submodules/TelegramUI/TelegramUI/ChatMessageAnimatedStickerItemNode.swift index 0981583fc3..d743672dd7 100644 --- a/submodules/TelegramUI/TelegramUI/ChatMessageAnimatedStickerItemNode.swift +++ b/submodules/TelegramUI/TelegramUI/ChatMessageAnimatedStickerItemNode.swift @@ -13,8 +13,10 @@ import AccountContext import MediaResources import StickerResources import ContextUI -import AnimationUI +import AnimatedStickerNode +import TelegramAnimatedStickerNode import Emoji +import Markdown private let nameFont = Font.medium(14.0) private let inlineBotPrefixFont = Font.regular(14.0) @@ -313,7 +315,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { if let file = file { let dimensions = file.dimensions ?? CGSize(width: 512.0, height: 512.0) let fittedSize = isEmoji ? dimensions.aspectFilled(CGSize(width: 384.0, height: 384.0)) : dimensions.aspectFitted(CGSize(width: 384.0, height: 384.0)) - self.animationNode.setup(resource: .resource(item.context.account, file.resource), fitzModifier: fitzModifier, width: Int(fittedSize.width), height: Int(fittedSize.height), playbackMode: playbackMode, mode: .cached) + self.animationNode.setup(source: AnimatedStickerResourceSource(account: item.context.account, resource: file.resource, fitzModifier: fitzModifier), width: Int(fittedSize.width), height: Int(fittedSize.height), playbackMode: playbackMode, mode: .cached) } } } diff --git a/submodules/TelegramUI/TelegramUI/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/TelegramUI/ChatMessageBubbleItemNode.swift index 0e072557bb..30c19b83f4 100644 --- a/submodules/TelegramUI/TelegramUI/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/TelegramUI/ChatMessageBubbleItemNode.swift @@ -20,6 +20,7 @@ import ReactionSelectionNode import PersistentStringHash import GridMessageSelectionNode import AppBundle +import Markdown private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> [(Message, AnyClass)] { var result: [(Message, AnyClass)] = [] diff --git a/submodules/TelegramUI/TelegramUI/ChatMessageInstantVideoItemNode.swift b/submodules/TelegramUI/TelegramUI/ChatMessageInstantVideoItemNode.swift index 509546afce..a00b50d8b4 100644 --- a/submodules/TelegramUI/TelegramUI/ChatMessageInstantVideoItemNode.swift +++ b/submodules/TelegramUI/TelegramUI/ChatMessageInstantVideoItemNode.swift @@ -11,6 +11,7 @@ import TextFormat import AccountContext import LocalizedPeerData import ContextUI +import Markdown private let nameFont = Font.medium(14.0) diff --git a/submodules/TelegramUI/TelegramUI/ChatMessageInteractiveMediaNode.swift b/submodules/TelegramUI/TelegramUI/ChatMessageInteractiveMediaNode.swift index 29fe6562bd..e2a7ebf344 100644 --- a/submodules/TelegramUI/TelegramUI/ChatMessageInteractiveMediaNode.swift +++ b/submodules/TelegramUI/TelegramUI/ChatMessageInteractiveMediaNode.swift @@ -16,7 +16,8 @@ import PhotoResources import TelegramUniversalVideoContent import TelegramStringFormatting import GalleryUI -import AnimationUI +import AnimatedStickerNode +import TelegramAnimatedStickerNode import LocalMediaResources import WallpaperResources @@ -731,7 +732,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio strongSelf.animatedStickerNode = animatedStickerNode let dimensions = updatedAnimatedStickerFile.dimensions ?? CGSize(width: 512.0, height: 512.0) let fittedDimensions = dimensions.aspectFitted(CGSize(width: 384.0, height: 384.0)) - animatedStickerNode.setup(resource: .resource(context.account, updatedAnimatedStickerFile.resource), width: Int(fittedDimensions.width), height: Int(fittedDimensions.height), mode: .cached) + animatedStickerNode.setup(source: AnimatedStickerResourceSource(account: context.account, resource: updatedAnimatedStickerFile.resource), width: Int(fittedDimensions.width), height: Int(fittedDimensions.height), mode: .cached) strongSelf.insertSubnode(animatedStickerNode, aboveSubnode: strongSelf.imageNode) animatedStickerNode.visibility = strongSelf.visibility } diff --git a/submodules/TelegramUI/TelegramUI/ChatMessageStickerItemNode.swift b/submodules/TelegramUI/TelegramUI/ChatMessageStickerItemNode.swift index 57d9f13a11..703f156b45 100644 --- a/submodules/TelegramUI/TelegramUI/ChatMessageStickerItemNode.swift +++ b/submodules/TelegramUI/TelegramUI/ChatMessageStickerItemNode.swift @@ -10,6 +10,7 @@ import TextFormat import AccountContext import StickerResources import ContextUI +import Markdown private let nameFont = Font.medium(14.0) private let inlineBotPrefixFont = Font.regular(14.0) diff --git a/submodules/TelegramUI/TelegramUI/ChatScheduleTimeControllerNode.swift b/submodules/TelegramUI/TelegramUI/ChatScheduleTimeControllerNode.swift index 9f7e591835..dce43d01f9 100644 --- a/submodules/TelegramUI/TelegramUI/ChatScheduleTimeControllerNode.swift +++ b/submodules/TelegramUI/TelegramUI/ChatScheduleTimeControllerNode.swift @@ -9,6 +9,7 @@ import TelegramStringFormatting import AccountContext import ShareController import SolidRoundedButtonNode +import PresentationDataUtils class ChatScheduleTimeControllerNode: ViewControllerTracingNode, UIScrollViewDelegate { private let context: AccountContext diff --git a/submodules/TelegramUI/TelegramUI/ChatSearchInputPanelNode.swift b/submodules/TelegramUI/TelegramUI/ChatSearchInputPanelNode.swift index b234f75dcc..026c55d013 100644 --- a/submodules/TelegramUI/TelegramUI/ChatSearchInputPanelNode.swift +++ b/submodules/TelegramUI/TelegramUI/ChatSearchInputPanelNode.swift @@ -54,7 +54,7 @@ final class ChatSearchInputPanelNode: ChatInputPanelNode { self.membersButton = HighlightableButtonNode() self.measureResultsLabel = TextNode() self.resultsButton = HighlightableButtonNode() - self.activityIndicator = ActivityIndicator(type: .navigationAccent(theme)) + self.activityIndicator = ActivityIndicator(type: .navigationAccent(theme.rootController.navigationBar.buttonColor)) self.activityIndicator.isHidden = true super.init() diff --git a/submodules/TelegramUI/TelegramUI/FetchCachedRepresentations.swift b/submodules/TelegramUI/TelegramUI/FetchCachedRepresentations.swift index f5a25fbd81..d9bcde2d84 100644 --- a/submodules/TelegramUI/TelegramUI/FetchCachedRepresentations.swift +++ b/submodules/TelegramUI/TelegramUI/FetchCachedRepresentations.swift @@ -16,7 +16,7 @@ import Lottie import MediaResources import PhotoResources import ImageBlur -import AnimationUI +import TelegramAnimatedStickerNode import WallpaperResources public func fetchCachedResourceRepresentation(account: Account, resource: MediaResource, representation: CachedMediaResourceRepresentation) -> Signal { diff --git a/submodules/TelegramUI/TelegramUI/HorizontalListContextResultsChatInputPanelItem.swift b/submodules/TelegramUI/TelegramUI/HorizontalListContextResultsChatInputPanelItem.swift index e3ec584cb3..5ae3c07858 100644 --- a/submodules/TelegramUI/TelegramUI/HorizontalListContextResultsChatInputPanelItem.swift +++ b/submodules/TelegramUI/TelegramUI/HorizontalListContextResultsChatInputPanelItem.swift @@ -9,7 +9,8 @@ import AVFoundation import RadialStatusNode import StickerResources import PhotoResources -import AnimationUI +import AnimatedStickerNode +import TelegramAnimatedStickerNode final class HorizontalListContextResultsChatInputPanelItem: ListViewItem { let account: Account @@ -386,7 +387,7 @@ final class HorizontalListContextResultsChatInputPanelItemNode: ListViewItemNode } let dimensions = animatedStickerFile.dimensions ?? CGSize(width: 512.0, height: 512.0) let fittedDimensions = dimensions.aspectFitted(CGSize(width: 160.0, height: 160.0)) - animationNode.setup(resource: .resource(item.account, animatedStickerFile.resource), width: Int(fittedDimensions.width), height: Int(fittedDimensions.height), mode: .cached) + animationNode.setup(source: AnimatedStickerResourceSource(account: item.account, resource: animatedStickerFile.resource), width: Int(fittedDimensions.width), height: Int(fittedDimensions.height), mode: .cached) } } diff --git a/submodules/TelegramUI/TelegramUI/HorizontalStickerGridItem.swift b/submodules/TelegramUI/TelegramUI/HorizontalStickerGridItem.swift index 1c7aad73b9..996aa3603e 100755 --- a/submodules/TelegramUI/TelegramUI/HorizontalStickerGridItem.swift +++ b/submodules/TelegramUI/TelegramUI/HorizontalStickerGridItem.swift @@ -7,7 +7,8 @@ import AsyncDisplayKit import Postbox import StickerResources import AccountContext -import AnimationUI +import AnimatedStickerNode +import TelegramAnimatedStickerNode final class HorizontalStickerGridItem: GridItem { let account: Account @@ -111,7 +112,7 @@ final class HorizontalStickerGridItemNode: GridItemNode { } let dimensions = item.file.dimensions ?? CGSize(width: 512.0, height: 512.0) let fittedDimensions = dimensions.aspectFitted(CGSize(width: 160.0, height: 160.0)) - animationNode.setup(resource: .resource(account, item.file.resource), width: Int(fittedDimensions.width), height: Int(fittedDimensions.height), mode: .cached) + animationNode.setup(source: AnimatedStickerResourceSource(account: account, resource: item.file.resource), width: Int(fittedDimensions.width), height: Int(fittedDimensions.height), mode: .cached) self.stickerFetchedDisposable.set(freeMediaFileResourceInteractiveFetched(account: account, fileReference: stickerPackFileReference(item.file), resource: item.file.resource).start()) } else { diff --git a/submodules/TelegramUI/TelegramUI/MediaInputPaneTrendingItem.swift b/submodules/TelegramUI/TelegramUI/MediaInputPaneTrendingItem.swift index 0329022ea9..1b5d3c2ee3 100644 --- a/submodules/TelegramUI/TelegramUI/MediaInputPaneTrendingItem.swift +++ b/submodules/TelegramUI/TelegramUI/MediaInputPaneTrendingItem.swift @@ -8,7 +8,8 @@ import TelegramCore import TelegramPresentationData import StickerResources import AccountContext -import AnimationUI +import AnimatedStickerNode +import TelegramAnimatedStickerNode class MediaInputPaneTrendingItem: ListViewItem { let account: Account @@ -118,7 +119,7 @@ final class TrendingTopItemNode: ASDisplayNode { } let dimensions = item.file.dimensions ?? CGSize(width: 512.0, height: 512.0) let fittedDimensions = dimensions.aspectFitted(CGSize(width: 160.0, height: 160.0)) - animationNode.setup(resource: .resource(account, item.file.resource), width: Int(fittedDimensions.width), height: Int(fittedDimensions.height), mode: .cached) + animationNode.setup(source: AnimatedStickerResourceSource(account: account, resource: item.file.resource), width: Int(fittedDimensions.width), height: Int(fittedDimensions.height), mode: .cached) self.loadDisposable.set(freeMediaFileResourceInteractiveFetched(account: account, fileReference: stickerPackFileReference(item.file), resource: item.file.resource).start()) } else { self.imageNode.setSignal(chatMessageSticker(account: account, file: item.file, small: true, synchronousLoad: synchronousLoads), attemptSynchronously: synchronousLoads) diff --git a/submodules/TelegramUI/TelegramUI/NotificationContentContext.swift b/submodules/TelegramUI/TelegramUI/NotificationContentContext.swift index fa30bf321d..4846c7d0eb 100644 --- a/submodules/TelegramUI/TelegramUI/NotificationContentContext.swift +++ b/submodules/TelegramUI/TelegramUI/NotificationContentContext.swift @@ -11,7 +11,8 @@ import AccountContext import Tuples import StickerResources import PhotoResources -import AnimationUI +import AnimatedStickerNode +import TelegramAnimatedStickerNode private enum NotificationContentAuthorizationError { case unauthorized @@ -313,7 +314,7 @@ public final class NotificationViewControllerImpl { let dimensions = fileReference.media.dimensions ?? CGSize(width: 512.0, height: 512.0) let fittedDimensions = dimensions.aspectFitted(CGSize(width: 512.0, height: 512.0)) strongSelf.imageNode.setSignal(chatMessageAnimatedSticker(postbox: accountAndImage.0.postbox, file: fileReference.media, small: false, size: fittedDimensions)) - animatedStickerNode.setup(resource: .resource(accountAndImage.0, fileReference.media.resource), width: Int(fittedDimensions.width), height: Int(fittedDimensions.height), mode: .direct) + animatedStickerNode.setup(source: AnimatedStickerResourceSource(account: accountAndImage.0, resource: fileReference.media.resource), width: Int(fittedDimensions.width), height: Int(fittedDimensions.height), mode: .direct) animatedStickerNode.visibility = true accountAndImage.0.network.shouldExplicitelyKeepWorkerConnections.set(.single(true)) diff --git a/submodules/TelegramUI/TelegramUI/OpenUrl.swift b/submodules/TelegramUI/TelegramUI/OpenUrl.swift index cb6e04a8ef..5519bf9769 100644 --- a/submodules/TelegramUI/TelegramUI/OpenUrl.swift +++ b/submodules/TelegramUI/TelegramUI/OpenUrl.swift @@ -15,6 +15,7 @@ import UrlEscaping import PassportUI import UrlHandling import WalletUI +import WalletUrl public struct ParsedSecureIdUrl { public let peerId: PeerId diff --git a/submodules/TelegramUI/TelegramUI/PeerSelectionController.swift b/submodules/TelegramUI/TelegramUI/PeerSelectionController.swift index 37f441d94b..244cb01ba1 100644 --- a/submodules/TelegramUI/TelegramUI/PeerSelectionController.swift +++ b/submodules/TelegramUI/TelegramUI/PeerSelectionController.swift @@ -29,7 +29,7 @@ public final class PeerSelectionControllerImpl: ViewController, PeerSelectionCon } if self.inProgress { - self.navigationItem.rightBarButtonItem = UIBarButtonItem(customDisplayNode: ProgressNavigationButtonNode(theme: self.presentationData.theme)) + self.navigationItem.rightBarButtonItem = UIBarButtonItem(customDisplayNode: ProgressNavigationButtonNode(color: self.presentationData.theme.rootController.navigationBar.controlColor)) } else { self.navigationItem.rightBarButtonItem = nil } diff --git a/submodules/TelegramUI/TelegramUI/Resources/PresentationStrings.mapping b/submodules/TelegramUI/TelegramUI/Resources/PresentationStrings.mapping index 75f4a93be4..e8a787649d 100644 Binary files a/submodules/TelegramUI/TelegramUI/Resources/PresentationStrings.mapping and b/submodules/TelegramUI/TelegramUI/Resources/PresentationStrings.mapping differ diff --git a/submodules/TelegramUI/TelegramUI/SharedAccountContext.swift b/submodules/TelegramUI/TelegramUI/SharedAccountContext.swift index fb4509b04a..d6788a0011 100644 --- a/submodules/TelegramUI/TelegramUI/SharedAccountContext.swift +++ b/submodules/TelegramUI/TelegramUI/SharedAccountContext.swift @@ -20,6 +20,7 @@ import LocalMediaResources import OverlayStatusController import AlertUI import PresentationDataUtils +import WalletCore private enum CallStatusText: Equatable { case none @@ -1031,7 +1032,7 @@ public final class SharedAccountContextImpl: SharedAccountContext { return } let _ = (combineLatest(queue: .mainQueue(), - availableWallets(postbox: context.account.postbox), + WalletStorageInterfaceImpl(postbox: context.account.postbox).getWalletRecords(), storedContext.keychain.encryptionPublicKey(), context.account.postbox.preferencesView(keys: [PreferencesKeys.appConfiguration]) ) @@ -1043,7 +1044,7 @@ public final class SharedAccountContextImpl: SharedAccountContext { } let tonContext = storedContext.context(config: config, blockchainName: blockchainName, enableProxy: !walletConfiguration.disableProxy) - if wallets.wallets.isEmpty { + if wallets.isEmpty { if case .send = walletContext { let presentationData = context.sharedContext.currentPresentationData.with { $0 } let controller = textAlertController(context: context, title: presentationData.strings.Conversation_WalletRequiredTitle, text: presentationData.strings.Conversation_WalletRequiredText, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Conversation_WalletRequiredNotNow, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.Conversation_WalletRequiredSetup, action: { [weak self] in @@ -1058,8 +1059,8 @@ public final class SharedAccountContextImpl: SharedAccountContext { } } } else { - let walletInfo = wallets.wallets[0].info - let exportCompleted = wallets.wallets[0].exportCompleted + let walletInfo = wallets[0].info + let exportCompleted = wallets[0].exportCompleted if let currentPublicKey = currentPublicKey { if currentPublicKey == walletInfo.encryptedSecret.publicKey { let _ = (walletAddress(publicKey: walletInfo.publicKey, tonInstance: tonContext.instance) diff --git a/submodules/TelegramUI/TelegramUI/StickerPaneSearchStickerItem.swift b/submodules/TelegramUI/TelegramUI/StickerPaneSearchStickerItem.swift index ff37014a28..86881e0b42 100644 --- a/submodules/TelegramUI/TelegramUI/StickerPaneSearchStickerItem.swift +++ b/submodules/TelegramUI/TelegramUI/StickerPaneSearchStickerItem.swift @@ -8,7 +8,8 @@ import Postbox import TelegramPresentationData import StickerResources import AccountContext -import AnimationUI +import AnimatedStickerNode +import TelegramAnimatedStickerNode final class StickerPaneSearchStickerSection: GridSection { let code: String @@ -164,7 +165,7 @@ final class StickerPaneSearchStickerItemNode: GridItemNode { } let dimensions = stickerItem.file.dimensions ?? CGSize(width: 512.0, height: 512.0) let fittedDimensions = dimensions.aspectFitted(CGSize(width: 160.0, height: 160.0)) - self.animationNode?.setup(resource: .resource(account, stickerItem.file.resource), width: Int(fittedDimensions.width), height: Int(fittedDimensions.height), mode: .cached) + self.animationNode?.setup(source: AnimatedStickerResourceSource(account: account, resource: stickerItem.file.resource), width: Int(fittedDimensions.width), height: Int(fittedDimensions.height), mode: .cached) self.animationNode?.visibility = self.isVisibleInGrid self.stickerFetchedDisposable.set(freeMediaFileResourceInteractiveFetched(account: account, fileReference: stickerPackFileReference(stickerItem.file), resource: stickerItem.file.resource).start()) } else { diff --git a/submodules/TelegramUI/TelegramUI/WalletContextImpl.swift b/submodules/TelegramUI/TelegramUI/WalletContextImpl.swift index 76406d0096..17b093f646 100644 --- a/submodules/TelegramUI/TelegramUI/WalletContextImpl.swift +++ b/submodules/TelegramUI/TelegramUI/WalletContextImpl.swift @@ -1,5 +1,6 @@ import Foundation import UIKit +import Display import WalletUI import Postbox import TelegramCore @@ -8,15 +9,98 @@ import SwiftSignalKit import TelegramPresentationData import ShareController import DeviceAccess +import PresentationDataUtils +import WalletCore + +extension WalletConfiguration { + static func with(appConfiguration: AppConfiguration) -> WalletConfiguration { + if let data = appConfiguration.data, let config = data["wallet_config"] as? String, let blockchainName = data["wallet_blockchain_name"] as? String { + var disableProxy = false + if let value = data["wallet_disable_proxy"] as? String { + disableProxy = value != "0" + } else if let value = data["wallet_disable_proxy"] as? Int { + disableProxy = value != 0 + } + return WalletConfiguration(config: config, blockchainName: blockchainName, disableProxy: disableProxy) + } else { + return .defaultValue + } + } +} + +final class WalletStorageInterfaceImpl: WalletStorageInterface { + private let postbox: Postbox + + init(postbox: Postbox) { + self.postbox = postbox + } + + func watchWalletRecords() -> Signal<[WalletStateRecord], NoError> { + return self.postbox.preferencesView(keys: [PreferencesKeys.walletCollection]) + |> map { view -> [WalletStateRecord] in + guard let walletCollection = view.values[PreferencesKeys.walletCollection] as? WalletCollection else { + return [] + } + return walletCollection.wallets.flatMap { item -> WalletStateRecord? in + do { + return WalletStateRecord(info: try JSONDecoder().decode(WalletInfo.self, from: item.info), exportCompleted: item.exportCompleted, state: item.state.flatMap { try? JSONDecoder().decode(CombinedWalletState.self, from: $0) }) + } catch { + return nil + } + } + } + } + + func getWalletRecords() -> Signal<[WalletStateRecord], NoError> { + return self.postbox.transaction { transaction -> [WalletStateRecord] in + guard let walletCollection = transaction.getPreferencesEntry(key: PreferencesKeys.walletCollection) as? WalletCollection else { + return [] + } + return walletCollection.wallets.flatMap { item -> WalletStateRecord? in + do { + return WalletStateRecord(info: try JSONDecoder().decode(WalletInfo.self, from: item.info), exportCompleted: item.exportCompleted, state: item.state.flatMap { try? JSONDecoder().decode(CombinedWalletState.self, from: $0) }) + } catch { + return nil + } + } + } + } + + func updateWalletRecords(_ f: @escaping ([WalletStateRecord]) -> [WalletStateRecord]) -> Signal<[WalletStateRecord], NoError> { + return self.postbox.transaction { transaction -> [WalletStateRecord] in + var updatedRecords: [WalletStateRecord] = [] + transaction.updatePreferencesEntry(key: PreferencesKeys.walletCollection, { current in + var walletCollection = (current as? WalletCollection) ?? WalletCollection(wallets: []) + let updatedItems = f(walletCollection.wallets.flatMap { item -> WalletStateRecord? in + do { + return WalletStateRecord(info: try JSONDecoder().decode(WalletInfo.self, from: item.info), exportCompleted: item.exportCompleted, state: item.state.flatMap { try? JSONDecoder().decode(CombinedWalletState.self, from: $0) }) + } catch { + return nil + } + }) + walletCollection.wallets = updatedItems.flatMap { item in + do { + return WalletCollectionItem(info: try JSONEncoder().encode(item.info), exportCompleted: item.exportCompleted, state: item.state.flatMap { + try? JSONEncoder().encode($0) + }) + } catch { + return nil + } + } + return walletCollection + }) + return updatedRecords + } + } +} final class WalletContextImpl: WalletContext { private let context: AccountContext - let postbox: Postbox - let network: Network + let storage: WalletStorageInterface let tonInstance: TonInstance let keychain: TonKeychain - let presentationData: PresentationData + let presentationData: WalletPresentationData var inForeground: Signal { return self.context.sharedContext.applicationBindings.applicationInForeground @@ -25,11 +109,97 @@ final class WalletContextImpl: WalletContext { init(context: AccountContext, tonContext: TonContext) { self.context = context - self.postbox = self.context.account.postbox - self.network = self.context.account.network + self.storage = WalletStorageInterfaceImpl(postbox: self.context.account.postbox) + self.tonInstance = tonContext.instance self.keychain = tonContext.keychain - self.presentationData = context.sharedContext.currentPresentationData.with { $0 } + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + let theme = presentationData.theme + let strings = presentationData.strings + let timeFormat: WalletTimeFormat + switch presentationData.dateTimeFormat.timeFormat { + case .military: + timeFormat = .military + case .regular: + timeFormat = .regular + } + let dateFormat: WalletDateFormat + switch presentationData.dateTimeFormat.dateFormat { + case .dayFirst: + dateFormat = .dayFirst + case .monthFirst: + dateFormat = .monthFirst + } + + let navigationBarData = NavigationBarPresentationData(presentationData: presentationData) + + self.presentationData = WalletPresentationData( + theme: WalletTheme( + info: WalletInfoTheme( + incomingFundsTitleColor: theme.chatList.secretTitleColor, + outgoingFundsTitleColor: theme.list.itemDestructiveColor + ), setup: WalletSetupTheme( + buttonFillColor: theme.list.itemCheckColors.fillColor, + buttonForegroundColor: theme.list.itemCheckColors.foregroundColor, + inputBackgroundColor: theme.actionSheet.inputBackgroundColor, + inputPlaceholderColor: theme.actionSheet.inputPlaceholderColor, + inputTextColor: theme.actionSheet.inputTextColor, + inputClearButtonColor: theme.actionSheet.inputClearButtonColor.withAlphaComponent(0.8) + ), + list: WalletListTheme( + itemPrimaryTextColor: theme.list.itemPrimaryTextColor, + itemSecondaryTextColor: theme.list.itemSecondaryTextColor, + itemPlaceholderTextColor: theme.list.itemPlaceholderTextColor, + itemDestructiveColor: theme.list.itemDestructiveColor, + itemAccentColor: theme.list.itemAccentColor, + itemDisabledTextColor: theme.list.itemDisabledTextColor, + plainBackgroundColor: theme.list.plainBackgroundColor, + blocksBackgroundColor: theme.list.blocksBackgroundColor, + itemPlainSeparatorColor: theme.list.itemPlainSeparatorColor, + itemBlocksBackgroundColor: theme.list.itemBlocksBackgroundColor, + itemBlocksSeparatorColor: theme.list.itemBlocksSeparatorColor, + itemHighlightedBackgroundColor: theme.list.itemHighlightedBackgroundColor, + sectionHeaderTextColor: theme.list.sectionHeaderTextColor, + freeTextColor: theme.list.freeTextColor, + freeTextErrorColor: theme.list.freeTextErrorColor, + inputClearButtonColor: theme.list.inputClearButtonColor + ), + statusBarStyle: theme.rootController.statusBarStyle.style, + navigationBar: navigationBarData.theme, + keyboardAppearance: theme.rootController.keyboardColor.keyboardAppearance, + alert: AlertControllerTheme(presentationTheme: theme), + actionSheet: ActionSheetControllerTheme(presentationTheme: theme) + ), strings: WalletStrings( + primaryComponent: WalletStringsComponent( + languageCode: strings.primaryComponent.languageCode, + localizedName: strings.primaryComponent.localizedName, + pluralizationRulesCode: strings.primaryComponent.pluralizationRulesCode, + dict: strings.primaryComponent.dict + ), + secondaryComponent: strings.secondaryComponent.flatMap { component in + return WalletStringsComponent( + languageCode: component.languageCode, + localizedName: component.localizedName, + pluralizationRulesCode: component.pluralizationRulesCode, + dict: component.dict + ) + }, + groupingSeparator: strings.groupingSeparator + ), dateTimeFormat: WalletPresentationDateTimeFormat( + timeFormat: timeFormat, + dateFormat: dateFormat, + dateSeparator: presentationData.dateTimeFormat.dateSeparator, + decimalSeparator: presentationData.dateTimeFormat.decimalSeparator, + groupingSeparator: presentationData.dateTimeFormat.groupingSeparator + ) + ) + } + + func getServerSalt() -> Signal { + return getServerWalletSalt(network: self.context.account.network) + |> mapError { _ -> WalletContextGetServerSaltError in + return .generic + } } func presentNativeController(_ controller: UIViewController) { diff --git a/submodules/TelegramUpdateUI/BUCK b/submodules/TelegramUpdateUI/BUCK index 760c462cc2..1a6144eb08 100644 --- a/submodules/TelegramUpdateUI/BUCK +++ b/submodules/TelegramUpdateUI/BUCK @@ -13,6 +13,7 @@ static_library( "//submodules/TelegramCore:TelegramCore#shared", "//submodules/TelegramPresentationData:TelegramPresentationData", "//submodules/ItemListUI:ItemListUI", + "//submodules/PresentationDataUtils:PresentationDataUtils", ], frameworks = [ "$SDKROOT/System/Library/Frameworks/Foundation.framework", diff --git a/submodules/TextFormat/BUCK b/submodules/TextFormat/BUCK index e5adc07eab..7f3a987751 100644 --- a/submodules/TextFormat/BUCK +++ b/submodules/TextFormat/BUCK @@ -9,6 +9,7 @@ static_library( "//submodules/TelegramCore:TelegramCore#dynamic", "//submodules/Display:Display#dynamic", "//submodules/TelegramPresentationData:TelegramPresentationData", + "//submodules/Markdown:Markdown", ], frameworks = [ "$SDKROOT/System/Library/Frameworks/Foundation.framework", diff --git a/submodules/TextFormat/Sources/AddFormatToStringWithRanges.swift b/submodules/TextFormat/Sources/AddFormatToStringWithRanges.swift index f0c2058f4e..f5d8de5bb7 100644 --- a/submodules/TextFormat/Sources/AddFormatToStringWithRanges.swift +++ b/submodules/TextFormat/Sources/AddFormatToStringWithRanges.swift @@ -1,5 +1,6 @@ import Foundation import UIKit +import Markdown public func addAttributesToStringWithRanges(_ stringWithRanges: (String, [(Int, NSRange)]), body: MarkdownAttributeSet, argumentAttributes: [Int: MarkdownAttributeSet], textAlignment: NSTextAlignment = .natural) -> NSAttributedString { let result = NSMutableAttributedString() diff --git a/submodules/UndoUI/BUCK b/submodules/UndoUI/BUCK index 2c9ca54a7d..0855d5c103 100644 --- a/submodules/UndoUI/BUCK +++ b/submodules/UndoUI/BUCK @@ -13,8 +13,10 @@ static_library( "//submodules/TelegramCore:TelegramCore#shared", "//submodules/TelegramPresentationData:TelegramPresentationData", "//submodules/TextFormat:TextFormat", + "//submodules/Markdown:Markdown", "//submodules/RadialStatusNode:RadialStatusNode", "//submodules/AnimationUI:AnimationUI", + "//submodules/AnimatedStickerNode:AnimatedStickerNode", "//submodules/AppBundle:AppBundle", ], frameworks = [ diff --git a/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift b/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift index 5975ba0bfb..b648b02e02 100644 --- a/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift +++ b/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift @@ -4,10 +4,12 @@ import AsyncDisplayKit import Display import SwiftSignalKit import TelegramPresentationData -import AnimationUI import TextFormat +import Markdown import RadialStatusNode import AppBundle +import AnimatedStickerNode +import AnimationUI final class UndoOverlayControllerNode: ViewControllerTracingNode { private let elevatedLayout: Bool @@ -126,7 +128,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode { self.animationNode = nil self.animatedStickerNode = AnimatedStickerNode() self.animatedStickerNode?.visibility = true - self.animatedStickerNode?.setup(resource: .localFile(path), width: 100, height: 100, playbackMode: .once, mode: .direct) + self.animatedStickerNode?.setup(source: AnimatedStickerNodeLocalFileSource(path: path), width: 100, height: 100, playbackMode: .once, mode: .direct) let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white) let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white) diff --git a/submodules/UrlHandling/BUCK b/submodules/UrlHandling/BUCK index 49ac625612..6023479e72 100644 --- a/submodules/UrlHandling/BUCK +++ b/submodules/UrlHandling/BUCK @@ -12,6 +12,7 @@ static_library( "//submodules/MtProtoKit:MtProtoKit#shared", "//submodules/AccountContext:AccountContext", "//submodules/TelegramUIPreferences:TelegramUIPreferences", + "//submodules/WalletUrl:WalletUrl", ], frameworks = [ "$SDKROOT/System/Library/Frameworks/Foundation.framework", diff --git a/submodules/UrlHandling/Sources/UrlHandling.swift b/submodules/UrlHandling/Sources/UrlHandling.swift index 0b0270935f..ae5bd673d3 100644 --- a/submodules/UrlHandling/Sources/UrlHandling.swift +++ b/submodules/UrlHandling/Sources/UrlHandling.swift @@ -11,6 +11,7 @@ import MtProtoKitDynamic import TelegramPresentationData import TelegramUIPreferences import AccountContext +import WalletUrl public enum ParsedInternalPeerUrlParameter { case botStart(String) @@ -435,42 +436,3 @@ public func resolveInstantViewUrl(account: Account, url: String) -> Signal Signal + public let encrypt: (Data) -> Signal + public let decrypt: (TonKeychainEncryptedData) -> Signal + + public init(encryptionPublicKey: @escaping () -> Signal, encrypt: @escaping (Data) -> Signal, decrypt: @escaping (TonKeychainEncryptedData) -> Signal) { + self.encryptionPublicKey = encryptionPublicKey + self.encrypt = encrypt + self.decrypt = decrypt + } +} + +public enum TonNetworkProxyResult { + case reponse(Data) + case error(String) +} + +public protocol TonNetworkProxy: class { + func request(data: Data, timeout: Double, completion: @escaping (TonNetworkProxyResult) -> Void) -> Disposable +} + +private final class TonInstanceImpl { + private let queue: Queue + private let basePath: String + private let config: String + private let blockchainName: String + private let proxy: TonNetworkProxy? + private var instance: TON? + + init(queue: Queue, basePath: String, config: String, blockchainName: String, proxy: TonNetworkProxy?) { + self.queue = queue + self.basePath = basePath + self.config = config + self.blockchainName = blockchainName + self.proxy = proxy + } + + func withInstance(_ f: (TON) -> Void) { + let instance: TON + if let current = self.instance { + instance = current + } else { + let proxy = self.proxy + instance = TON(keystoreDirectory: self.basePath + "/ton-keystore", config: self.config, blockchainName: self.blockchainName, performExternalRequest: { request in + if let proxy = proxy { + let _ = proxy.request(data: request.data, timeout: 20.0, completion: { result in + switch result { + case let .reponse(data): + request.onResult(data, nil) + case let .error(description): + request.onResult(nil, description) + } + }) + } else { + request.onResult(nil, "NETWORK_DISABLED") + } + }, enableExternalRequests: proxy != nil) + self.instance = instance + } + f(instance) + } +} + +public final class TonInstance { + private let queue: Queue + private let impl: QueueLocalObject + + public init(basePath: String, config: String, blockchainName: String, proxy: TonNetworkProxy?) { + self.queue = .mainQueue() + let queue = self.queue + self.impl = QueueLocalObject(queue: queue, generate: { + return TonInstanceImpl(queue: queue, basePath: basePath, config: config, blockchainName: blockchainName, proxy: proxy) + }) + } + + fileprivate func exportKey(key: TONKey, localPassword: Data) -> Signal<[String], NoError> { + return Signal { subscriber in + let disposable = MetaDisposable() + + self.impl.with { impl in + impl.withInstance { ton in + let cancel = ton.export(key, localPassword: localPassword).start(next: { wordList in + guard let wordList = wordList as? [String] else { + assertionFailure() + return + } + subscriber.putNext(wordList) + subscriber.putCompletion() + }) + disposable.set(ActionDisposable { + cancel?.dispose() + }) + } + } + + return disposable + } + } + + fileprivate func createWallet(keychain: TonKeychain, localPassword: Data) -> Signal<(WalletInfo, [String]), CreateWalletError> { + return Signal { subscriber in + let disposable = MetaDisposable() + self.impl.with { impl in + impl.withInstance { ton in + let cancel = ton.createKey(withLocalPassword: localPassword, mnemonicPassword: Data()).start(next: { key in + guard let key = key as? TONKey else { + assertionFailure() + return + } + let cancel = keychain.encrypt(key.secret).start(next: { encryptedSecretData in + let _ = self.exportKey(key: key, localPassword: localPassword).start(next: { wordList in + subscriber.putNext((WalletInfo(publicKey: WalletPublicKey(rawValue: key.publicKey), encryptedSecret: encryptedSecretData), wordList)) + subscriber.putCompletion() + }, error: { error in + subscriber.putError(.generic) + }) + }, error: { _ in + subscriber.putError(.generic) + }, completed: { + }) + }, error: { _ in + }, completed: { + }) + disposable.set(ActionDisposable { + cancel?.dispose() + }) + } + } + + return disposable + } + } + + fileprivate func importWallet(keychain: TonKeychain, wordList: [String], localPassword: Data) -> Signal { + return Signal { subscriber in + let disposable = MetaDisposable() + + self.impl.with { impl in + impl.withInstance { ton in + let cancel = ton.importKey(withLocalPassword: localPassword, mnemonicPassword: Data(), wordList: wordList).start(next: { key in + guard let key = key as? TONKey else { + subscriber.putError(.generic) + return + } + let cancel = keychain.encrypt(key.secret).start(next: { encryptedSecretData in + subscriber.putNext(WalletInfo(publicKey: WalletPublicKey(rawValue: key.publicKey), encryptedSecret: encryptedSecretData)) + subscriber.putCompletion() + }, error: { _ in + subscriber.putError(.generic) + }, completed: { + }) + }, error: { _ in + subscriber.putError(.generic) + }, completed: { + }) + disposable.set(ActionDisposable { + cancel?.dispose() + }) + } + } + + return disposable + } + } + + fileprivate func walletAddress(publicKey: WalletPublicKey) -> Signal { + return Signal { subscriber in + let disposable = MetaDisposable() + + self.impl.with { impl in + impl.withInstance { ton in + let cancel = ton.getWalletAccountAddress(withPublicKey: publicKey.rawValue).start(next: { address in + guard let address = address as? String else { + return + } + subscriber.putNext(address) + subscriber.putCompletion() + }, error: { _ in + }, completed: { + }) + disposable.set(ActionDisposable { + cancel?.dispose() + }) + } + } + + return disposable + } + } + + private func getWalletStateRaw(address: String) -> Signal { + return Signal { subscriber in + let disposable = MetaDisposable() + + self.impl.with { impl in + impl.withInstance { ton in + let cancel = ton.getAccountState(withAddress: address).start(next: { state in + guard let state = state as? TONAccountState else { + return + } + subscriber.putNext(state) + }, error: { error in + if let error = error as? TONError { + if error.text.hasPrefix("LITE_SERVER_") { + subscriber.putError(.network) + } else { + subscriber.putError(.generic) + } + } else { + subscriber.putError(.generic) + } + }, completed: { + subscriber.putCompletion() + }) + disposable.set(ActionDisposable { + cancel?.dispose() + }) + } + } + + return disposable + } + } + + fileprivate func getWalletState(address: String) -> Signal<(WalletState, Int64), GetWalletStateError> { + return self.getWalletStateRaw(address: address) + |> map { state in + return (WalletState(balance: state.balance, lastTransactionId: state.lastTransactionId.flatMap(WalletTransactionId.init(tonTransactionId:))), state.syncUtime) + } + } + + fileprivate func walletLastTransactionId(address: String) -> Signal { + return Signal { subscriber in + let disposable = MetaDisposable() + + self.impl.with { impl in + impl.withInstance { ton in + let cancel = ton.getAccountState(withAddress: address).start(next: { state in + guard let state = state as? TONAccountState else { + subscriber.putNext(nil) + return + } + subscriber.putNext(state.lastTransactionId.flatMap(WalletTransactionId.init(tonTransactionId:))) + }, error: { error in + if let error = error as? TONError { + if error.text.hasPrefix("ДITE_SERVER_") { + subscriber.putError(.network) + } else { + subscriber.putError(.generic) + } + } else { + subscriber.putError(.generic) + } + }, completed: { + subscriber.putCompletion() + }) + disposable.set(ActionDisposable { + cancel?.dispose() + }) + } + } + + return disposable + } + } + + fileprivate func getWalletTransactions(address: String, previousId: WalletTransactionId) -> Signal<[WalletTransaction], GetWalletTransactionsError> { + return Signal { subscriber in + let disposable = MetaDisposable() + + self.impl.with { impl in + impl.withInstance { ton in + let cancel = ton.getTransactionList(withAddress: address, lt: previousId.lt, hash: previousId.transactionHash).start(next: { transactions in + guard let transactions = transactions as? [TONTransaction] else { + subscriber.putError(.generic) + return + } + subscriber.putNext(transactions.map(WalletTransaction.init(tonTransaction:))) + }, error: { error in + if let error = error as? TONError { + if error.text.hasPrefix("LITE_SERVER_") { + subscriber.putError(.network) + } else { + subscriber.putError(.generic) + } + } else { + subscriber.putError(.generic) + } + }, completed: { + subscriber.putCompletion() + }) + disposable.set(ActionDisposable { + cancel?.dispose() + }) + } + } + + return disposable + } + } + + fileprivate func sendGramsFromWallet(decryptedSecret: Data, localPassword: Data, walletInfo: WalletInfo, fromAddress: String, toAddress: String, amount: Int64, textMessage: Data, forceIfDestinationNotInitialized: Bool, timeout: Int32, randomId: Int64) -> Signal { + let key = TONKey(publicKey: walletInfo.publicKey.rawValue, secret: decryptedSecret) + return Signal { subscriber in + let disposable = MetaDisposable() + + self.impl.with { impl in + impl.withInstance { ton in + let cancel = ton.sendGrams(from: key, localPassword: localPassword, fromAddress: fromAddress, toAddress: toAddress, amount: amount, textMessage: textMessage, forceIfDestinationNotInitialized: forceIfDestinationNotInitialized, timeout: timeout, randomId: randomId).start(next: { result in + guard let result = result as? TONSendGramsResult else { + subscriber.putError(.generic) + return + } + subscriber.putNext(PendingWalletTransaction(timestamp: Int64(Date().timeIntervalSince1970), validUntilTimestamp: result.sentUntil, bodyHash: result.bodyHash, address: toAddress, value: amount, comment: textMessage)) + subscriber.putCompletion() + }, error: { error in + if let error = error as? TONError { + if error.text.hasPrefix("INVALID_ACCOUNT_ADDRESS") { + subscriber.putError(.invalidAddress) + } else if error.text.hasPrefix("DANGEROUS_TRANSACTION") { + subscriber.putError(.destinationIsNotInitialized) + } else if error.text.hasPrefix("MESSAGE_TOO_LONG") { + subscriber.putError(.messageTooLong) + } else if error.text.hasPrefix("NOT_ENOUGH_FUNDS") { + subscriber.putError(.notEnoughFunds) + } else if error.text.hasPrefix("LITE_SERVER_") { + subscriber.putError(.network) + } else { + subscriber.putError(.generic) + } + } else { + subscriber.putError(.generic) + } + }, completed: { + subscriber.putCompletion() + }) + disposable.set(ActionDisposable { + cancel?.dispose() + }) + } + } + + return disposable + } + } + + fileprivate func walletRestoreWords(publicKey: WalletPublicKey, decryptedSecret: Data, localPassword: Data) -> Signal<[String], WalletRestoreWordsError> { + return Signal { subscriber in + let disposable = MetaDisposable() + + self.impl.with { impl in + impl.withInstance { ton in + let cancel = ton.export(TONKey(publicKey: publicKey.rawValue, secret: decryptedSecret), localPassword: localPassword).start(next: { wordList in + guard let wordList = wordList as? [String] else { + subscriber.putError(.generic) + return + } + subscriber.putNext(wordList) + }, error: { _ in + subscriber.putError(.generic) + }, completed: { + subscriber.putCompletion() + }) + disposable.set(ActionDisposable { + cancel?.dispose() + }) + } + } + + return disposable + } + } + + fileprivate func deleteAllLocalWalletsData() -> Signal { + return Signal { subscriber in + let disposable = MetaDisposable() + + self.impl.with { impl in + impl.withInstance { ton in + let cancel = ton.deleteAllKeys().start(next: { _ in + assertionFailure() + }, error: { _ in + subscriber.putError(.generic) + }, completed: { + subscriber.putCompletion() + }) + disposable.set(ActionDisposable { + cancel?.dispose() + }) + } + } + + return disposable + } + } + fileprivate func encrypt(_ decryptedData: Data, secret: Data) -> Signal { + return Signal { subscriber in + let disposable = MetaDisposable() + + self.impl.with { impl in + impl.withInstance { ton in + subscriber.putNext(ton.encrypt(decryptedData, secret: secret)) + subscriber.putCompletion() + } + } + + return disposable + } + } + fileprivate func decrypt(_ encryptedData: Data, secret: Data) -> Signal { + return Signal { subscriber in + let disposable = MetaDisposable() + + self.impl.with { impl in + impl.withInstance { ton in + subscriber.putNext(ton.decrypt(encryptedData, secret: secret)) + subscriber.putCompletion() + } + } + + return disposable + } + } +} + +public struct WalletPublicKey: Codable, Hashable { + public var rawValue: String + + public init(rawValue: String) { + self.rawValue = rawValue + } +} + +public struct WalletInfo: Codable, Equatable { + public let publicKey: WalletPublicKey + public let encryptedSecret: TonKeychainEncryptedData + + public init(publicKey: WalletPublicKey, encryptedSecret: TonKeychainEncryptedData) { + self.publicKey = publicKey + self.encryptedSecret = encryptedSecret + } +} + +public struct CombinedWalletState: Codable, Equatable { + public var walletState: WalletState + public var timestamp: Int64 + public var topTransactions: [WalletTransaction] + public var pendingTransactions: [PendingWalletTransaction] +} + +public struct WalletStateRecord: Equatable { + public let info: WalletInfo + public var exportCompleted: Bool + public var state: CombinedWalletState? + + public init(info: WalletInfo, exportCompleted: Bool, state: CombinedWalletState?) { + self.info = info + self.exportCompleted = exportCompleted + self.state = state + } +} + +public enum CreateWalletError { + case generic +} + +public func tonlibEncrypt(tonInstance: TonInstance, decryptedData: Data, secret: Data) -> Signal { + return tonInstance.encrypt(decryptedData, secret: secret) +} +public func tonlibDecrypt(tonInstance: TonInstance, encryptedData: Data, secret: Data) -> Signal { + return tonInstance.decrypt(encryptedData, secret: secret) +} + +public func createWallet(storage: WalletStorageInterface, tonInstance: TonInstance, keychain: TonKeychain, localPassword: Data) -> Signal<(WalletInfo, [String]), CreateWalletError> { + return tonInstance.createWallet(keychain: keychain, localPassword: localPassword) + |> mapToSignal { walletInfo, wordList -> Signal<(WalletInfo, [String]), CreateWalletError> in + return storage.updateWalletRecords({ records in + var records = records + records.append(WalletStateRecord(info: walletInfo, exportCompleted: false, state: nil)) + return records + }) + |> map { _ -> (WalletInfo, [String]) in + return (walletInfo, wordList) + } + |> castError(CreateWalletError.self) + } +} + +public func confirmWalletExported(storage: WalletStorageInterface, publicKey: WalletPublicKey) -> Signal { + return storage.updateWalletRecords { records in + var records = records + for i in 0 ..< records.count { + if records[i].info.publicKey == publicKey { + records[i].exportCompleted = true + } + } + return records + } + |> ignoreValues +} + +private enum ImportWalletInternalError { + case generic +} + +public enum ImportWalletError { + case generic +} + +public func importWallet(storage: WalletStorageInterface, tonInstance: TonInstance, keychain: TonKeychain, wordList: [String], localPassword: Data) -> Signal { + return tonInstance.importWallet(keychain: keychain, wordList: wordList, localPassword: localPassword) + |> `catch` { error -> Signal in + switch error { + case .generic: + return .fail(.generic) + } + } + |> mapToSignal { walletInfo -> Signal in + return storage.updateWalletRecords { records in + var records = records + records.append(WalletStateRecord(info: walletInfo, exportCompleted: true, state: nil)) + return records + } + |> map { _ -> WalletInfo in + return walletInfo + } + |> castError(ImportWalletError.self) + } +} + +public enum DeleteAllLocalWalletsDataError { + case generic +} + +public func deleteAllLocalWalletsData(storage: WalletStorageInterface, tonInstance: TonInstance) -> Signal { + return tonInstance.deleteAllLocalWalletsData() + |> `catch` { _ -> Signal in + return .complete() + } + |> then( + storage.updateWalletRecords { _ in [] } + |> castError(DeleteAllLocalWalletsDataError.self) + |> ignoreValues + ) +} + +public enum WalletRestoreWordsError { + case generic +} + +public func walletRestoreWords(tonInstance: TonInstance, publicKey: WalletPublicKey, decryptedSecret: Data, localPassword: Data) -> Signal<[String], WalletRestoreWordsError> { + return tonInstance.walletRestoreWords(publicKey: publicKey, decryptedSecret: decryptedSecret, localPassword: localPassword) +} + +public struct WalletState: Codable, Equatable { + public let balance: Int64 + public let lastTransactionId: WalletTransactionId? + + public init(balance: Int64, lastTransactionId: WalletTransactionId?) { + self.balance = balance + self.lastTransactionId = lastTransactionId + } +} + +public func walletAddress(publicKey: WalletPublicKey, tonInstance: TonInstance) -> Signal { + return tonInstance.walletAddress(publicKey: publicKey) +} + +private enum GetWalletStateError { + case generic + case network +} + +private func getWalletState(address: String, tonInstance: TonInstance) -> Signal<(WalletState, Int64), GetWalletStateError> { + return tonInstance.getWalletState(address: address) +} + +public enum GetCombinedWalletStateError { + case generic + case network +} + +public enum CombinedWalletStateResult { + case cached(CombinedWalletState?) + case updated(CombinedWalletState) +} + +public enum CombinedWalletStateSubject { + case wallet(WalletInfo) + case address(String) +} + +public func getCombinedWalletState(storage: WalletStorageInterface, subject: CombinedWalletStateSubject, tonInstance: TonInstance, onlyCached: Bool = false) -> Signal { + switch subject { + case let .wallet(walletInfo): + return storage.getWalletRecords() + |> map { records -> CombinedWalletState? in + for item in records { + if item.info.publicKey == walletInfo.publicKey { + return item.state + } + } + return nil + } + |> castError(GetCombinedWalletStateError.self) + |> mapToSignal { cachedState -> Signal in + if onlyCached { + return .single(.cached(cachedState)) + } + return .single(.cached(cachedState)) + |> then( + tonInstance.walletAddress(publicKey: walletInfo.publicKey) + |> castError(GetCombinedWalletStateError.self) + |> mapToSignal { address -> Signal in + return getWalletState(address: address, tonInstance: tonInstance) + |> retryTonRequest(isNetworkError: { error in + if case .network = error { + return true + } else { + return false + } + }) + |> mapError { error -> GetCombinedWalletStateError in + if case .network = error { + return .network + } else { + return .generic + } + } + |> mapToSignal { walletState, syncUtime -> Signal in + let topTransactions: Signal<[WalletTransaction], GetCombinedWalletStateError> + if walletState.lastTransactionId == cachedState?.walletState.lastTransactionId { + topTransactions = .single(cachedState?.topTransactions ?? []) + } else { + topTransactions = getWalletTransactions(address: address, previousId: nil, tonInstance: tonInstance) + |> mapError { error -> GetCombinedWalletStateError in + if case .network = error { + return .network + } else { + return .generic + } + } + } + return topTransactions + |> mapToSignal { topTransactions -> Signal in + let lastTransactionTimestamp = topTransactions.last?.timestamp + var listTransactionBodyHashes = Set() + for transaction in topTransactions { + if let message = transaction.inMessage { + listTransactionBodyHashes.insert(message.bodyHash) + } + for message in transaction.outMessages { + listTransactionBodyHashes.insert(message.bodyHash) + } + } + let pendingTransactions = (cachedState?.pendingTransactions ?? []).filter { transaction in + if transaction.validUntilTimestamp <= syncUtime { + return false + } else if let lastTransactionTimestamp = lastTransactionTimestamp, transaction.validUntilTimestamp <= lastTransactionTimestamp { + return false + } else { + if listTransactionBodyHashes.contains(transaction.bodyHash) { + return false + } + return true + } + } + let combinedState = CombinedWalletState(walletState: walletState, timestamp: syncUtime, topTransactions: topTransactions, pendingTransactions: pendingTransactions) + + return storage.updateWalletRecords { records in + var records = records + for i in 0 ..< records.count { + if records[i].info.publicKey == walletInfo.publicKey { + records[i].state = combinedState + } + } + return records + } + |> map { _ -> CombinedWalletStateResult in + return .updated(combinedState) + } + |> castError(GetCombinedWalletStateError.self) + } + } + } + ) + } + case let .address(address): + let updated = getWalletState(address: address, tonInstance: tonInstance) + |> mapError { _ -> GetCombinedWalletStateError in + return .generic + } + |> mapToSignal { walletState, syncUtime -> Signal in + let topTransactions: Signal<[WalletTransaction], GetCombinedWalletStateError> + + topTransactions = getWalletTransactions(address: address, previousId: nil, tonInstance: tonInstance) + |> mapError { _ -> GetCombinedWalletStateError in + return .generic + } + return topTransactions + |> mapToSignal { topTransactions -> Signal in + let combinedState = CombinedWalletState(walletState: walletState, timestamp: syncUtime, topTransactions: topTransactions, pendingTransactions: []) + return .single(.updated(combinedState)) + } + } + return .single(.cached(nil)) + |> then(updated) + } +} + +public enum SendGramsFromWalletError { + case generic + case secretDecryptionFailed + case invalidAddress + case destinationIsNotInitialized + case messageTooLong + case notEnoughFunds + case network +} + +public func sendGramsFromWallet(storage: WalletStorageInterface, tonInstance: TonInstance, walletInfo: WalletInfo, decryptedSecret: Data, localPassword: Data, toAddress: String, amount: Int64, textMessage: Data, forceIfDestinationNotInitialized: Bool, timeout: Int32, randomId: Int64) -> Signal<[PendingWalletTransaction], SendGramsFromWalletError> { + return walletAddress(publicKey: walletInfo.publicKey, tonInstance: tonInstance) + |> castError(SendGramsFromWalletError.self) + |> mapToSignal { fromAddress -> Signal<[PendingWalletTransaction], SendGramsFromWalletError> in + return tonInstance.sendGramsFromWallet(decryptedSecret: decryptedSecret, localPassword: localPassword, walletInfo: walletInfo, fromAddress: fromAddress, toAddress: toAddress, amount: amount, textMessage: textMessage, forceIfDestinationNotInitialized: forceIfDestinationNotInitialized, timeout: timeout, randomId: randomId) + |> mapToSignal { result -> Signal<[PendingWalletTransaction], SendGramsFromWalletError> in + return storage.updateWalletRecords { records in + var records = records + for i in 0 ..< records.count { + if records[i].info.publicKey == walletInfo.publicKey { + if var state = records[i].state { + state.pendingTransactions.insert(result, at: 0) + records[i].state = state + } + } + } + return records + } + |> map { records -> [PendingWalletTransaction] in + for i in 0 ..< records.count { + if records[i].info.publicKey == walletInfo.publicKey { + if let state = records[i].state { + return state.pendingTransactions + } + } + } + return [] + } + |> castError(SendGramsFromWalletError.self) + } + } +} + +public struct WalletTransactionId: Codable, Hashable { + public var lt: Int64 + public var transactionHash: Data +} + +private extension WalletTransactionId { + init(tonTransactionId: TONTransactionId) { + self.lt = tonTransactionId.lt + self.transactionHash = tonTransactionId.transactionHash + } +} + +public final class WalletTransactionMessage: Codable, Equatable { + public let value: Int64 + public let source: String + public let destination: String + public let textMessage: String + public let bodyHash: Data + + init(value: Int64, source: String, destination: String, textMessage: String, bodyHash: Data) { + self.value = value + self.source = source + self.destination = destination + self.textMessage = textMessage + self.bodyHash = bodyHash + } + + public static func ==(lhs: WalletTransactionMessage, rhs: WalletTransactionMessage) -> Bool { + if lhs.value != rhs.value { + return false + } + if lhs.source != rhs.source { + return false + } + if lhs.destination != rhs.destination { + return false + } + if lhs.textMessage != rhs.textMessage { + return false + } + if lhs.bodyHash != rhs.bodyHash { + return false + } + return true + } +} + +private extension WalletTransactionMessage { + convenience init(tonTransactionMessage: TONTransactionMessage) { + self.init(value: tonTransactionMessage.value, source: tonTransactionMessage.source, destination: tonTransactionMessage.destination, textMessage: tonTransactionMessage.textMessage, bodyHash: tonTransactionMessage.bodyHash) + } +} + +public final class PendingWalletTransaction: Codable, Equatable { + public let timestamp: Int64 + public let validUntilTimestamp: Int64 + public let bodyHash: Data + public let address: String + public let value: Int64 + public let comment: Data + + public init(timestamp: Int64, validUntilTimestamp: Int64, bodyHash: Data, address: String, value: Int64, comment: Data) { + self.timestamp = timestamp + self.validUntilTimestamp = validUntilTimestamp + self.bodyHash = bodyHash + self.address = address + self.value = value + self.comment = comment + } + + public static func ==(lhs: PendingWalletTransaction, rhs: PendingWalletTransaction) -> Bool { + if lhs.timestamp != rhs.timestamp { + return false + } + if lhs.validUntilTimestamp != rhs.validUntilTimestamp { + return false + } + if lhs.bodyHash != rhs.bodyHash { + return false + } + if lhs.value != rhs.value { + return false + } + if lhs.comment != rhs.comment { + return false + } + return true + } +} + +public final class WalletTransaction: Codable, Equatable { + public let data: Data + public let transactionId: WalletTransactionId + public let timestamp: Int64 + public let storageFee: Int64 + public let otherFee: Int64 + public let inMessage: WalletTransactionMessage? + public let outMessages: [WalletTransactionMessage] + + public var transferredValueWithoutFees: Int64 { + var value: Int64 = 0 + if let inMessage = self.inMessage { + value += inMessage.value + } + for message in self.outMessages { + value -= message.value + } + return value + } + + init(data: Data, transactionId: WalletTransactionId, timestamp: Int64, storageFee: Int64, otherFee: Int64, inMessage: WalletTransactionMessage?, outMessages: [WalletTransactionMessage]) { + self.data = data + self.transactionId = transactionId + self.timestamp = timestamp + self.storageFee = storageFee + self.otherFee = otherFee + self.inMessage = inMessage + self.outMessages = outMessages + } + + public static func ==(lhs: WalletTransaction, rhs: WalletTransaction) -> Bool { + if lhs.data != rhs.data { + return false + } + if lhs.transactionId != rhs.transactionId { + return false + } + if lhs.timestamp != rhs.timestamp { + return false + } + if lhs.storageFee != rhs.storageFee { + return false + } + if lhs.otherFee != rhs.otherFee { + return false + } + if lhs.inMessage != rhs.inMessage { + return false + } + if lhs.outMessages != rhs.outMessages { + return false + } + return true + } +} + +private extension WalletTransaction { + convenience init(tonTransaction: TONTransaction) { + self.init(data: tonTransaction.data, transactionId: WalletTransactionId(tonTransactionId: tonTransaction.transactionId), timestamp: tonTransaction.timestamp, storageFee: tonTransaction.storageFee, otherFee: tonTransaction.otherFee, inMessage: tonTransaction.inMessage.flatMap(WalletTransactionMessage.init(tonTransactionMessage:)), outMessages: tonTransaction.outMessages.map(WalletTransactionMessage.init(tonTransactionMessage:))) + } +} + +public enum GetWalletTransactionsError { + case generic + case network +} + +public func getWalletTransactions(address: String, previousId: WalletTransactionId?, tonInstance: TonInstance) -> Signal<[WalletTransaction], GetWalletTransactionsError> { + return getWalletTransactionsOnce(address: address, previousId: previousId, tonInstance: tonInstance) + |> mapToSignal { transactions in + guard let lastTransaction = transactions.last, transactions.count >= 2 else { + return .single(transactions) + } + return getWalletTransactionsOnce(address: address, previousId: lastTransaction.transactionId, tonInstance: tonInstance) + |> map { additionalTransactions in + var result = transactions + var existingIds = Set(result.map { $0.transactionId }) + for transaction in additionalTransactions { + if !existingIds.contains(transaction.transactionId) { + existingIds.insert(transaction.transactionId) + result.append(transaction) + } + } + return result + } + } +} + +private func retryTonRequest(isNetworkError: @escaping (E) -> Bool) -> (Signal) -> Signal { + return { signal in + return signal + |> retry(retryOnError: isNetworkError, delayIncrement: 0.2, maxDelay: 5.0, maxRetries: 3, onQueue: Queue.concurrentDefaultQueue()) + } +} + +private enum WalletLastTransactionIdError { + case generic + case network +} + +private func getWalletTransactionsOnce(address: String, previousId: WalletTransactionId?, tonInstance: TonInstance) -> Signal<[WalletTransaction], GetWalletTransactionsError> { + let previousIdValue: Signal + if let previousId = previousId { + previousIdValue = .single(previousId) + } else { + previousIdValue = tonInstance.walletLastTransactionId(address: address) + |> retryTonRequest(isNetworkError: { error in + if case .network = error { + return true + } else { + return false + } + }) + |> mapError { error -> GetWalletTransactionsError in + if case .network = error { + return .network + } else { + return .generic + } + } + } + return previousIdValue + |> mapToSignal { previousId in + if let previousId = previousId { + return tonInstance.getWalletTransactions(address: address, previousId: previousId) + |> retryTonRequest(isNetworkError: { error in + if case .network = error { + return true + } else { + return false + } + }) + } else { + return .single([]) + } + } +} + +public protocol WalletStorageInterface { + func watchWalletRecords() -> Signal<[WalletStateRecord], NoError> + func getWalletRecords() -> Signal<[WalletStateRecord], NoError> + func updateWalletRecords(_ f: @escaping ([WalletStateRecord]) -> [WalletStateRecord]) -> Signal<[WalletStateRecord], NoError> +} diff --git a/submodules/WalletUI/.BUCK.swp b/submodules/WalletUI/.BUCK.swp deleted file mode 100644 index c6e6a812f9..0000000000 Binary files a/submodules/WalletUI/.BUCK.swp and /dev/null differ diff --git a/submodules/WalletUI/BUCK b/submodules/WalletUI/BUCK index 1a770c2a68..670e2185ab 100644 --- a/submodules/WalletUI/BUCK +++ b/submodules/WalletUI/BUCK @@ -8,6 +8,14 @@ apple_resource( visibility = ["PUBLIC"], ) +apple_asset_catalog( + name = 'WalletUIAssets', + dirs = [ + "Images.xcassets", + ], + visibility = ["PUBLIC"], +) + static_library( name = "WalletUI", srcs = glob([ @@ -17,24 +25,24 @@ static_library( "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit#shared", "//submodules/AsyncDisplayKit:AsyncDisplayKit#shared", "//submodules/Display:Display#shared", - "//submodules/Postbox:Postbox#shared", - "//submodules/TelegramCore:TelegramCore#shared", "//submodules/OverlayStatusController:OverlayStatusController", "//submodules/AppBundle:AppBundle", "//submodules/SolidRoundedButtonNode:SolidRoundedButtonNode", - "//submodules/UndoUI:UndoUI", "//submodules/AlertUI:AlertUI", - "//submodules/TextFormat:TextFormat", "//submodules/Camera:Camera", - "//submodules/PasscodeInputFieldNode:PasscodeInputFieldNode", "//submodules/QrCode:QrCode", "//submodules/MergeLists:MergeLists", "//submodules/GlassButtonNode:GlassButtonNode", - "//submodules/UrlHandling:UrlHandling", "//submodules/UrlEscaping:UrlEscaping", "//submodules/LocalAuth:LocalAuth", "//submodules/ScreenCaptureDetection:ScreenCaptureDetection", - "//submodules/AnimationUI:AnimationUI", + "//submodules/AnimatedStickerNode:AnimatedStickerNode", + "//submodules/WalletUrl:WalletUrl", + "//submodules/WalletCore:WalletCore", + "//submodules/StringPluralization:StringPluralization", + "//submodules/ActivityIndicator:ActivityIndicator", + "//submodules/ProgressNavigationButtonNode:ProgressNavigationButtonNode", + "//submodules/Markdown:Markdown", ], frameworks = [ "$SDKROOT/System/Library/Frameworks/Foundation.framework", diff --git a/submodules/WalletUI/Images.xcassets/Contents.json b/submodules/WalletUI/Images.xcassets/Contents.json new file mode 100644 index 0000000000..da4a164c91 --- /dev/null +++ b/submodules/WalletUI/Images.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/submodules/WalletUI/Images.xcassets/Wallet/BalanceGem.imageset/Contents.json b/submodules/WalletUI/Images.xcassets/Wallet/BalanceGem.imageset/Contents.json new file mode 100644 index 0000000000..8b3d54c512 --- /dev/null +++ b/submodules/WalletUI/Images.xcassets/Wallet/BalanceGem.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "gem.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/submodules/WalletUI/Images.xcassets/Wallet/BalanceGem.imageset/gem.pdf b/submodules/WalletUI/Images.xcassets/Wallet/BalanceGem.imageset/gem.pdf new file mode 100644 index 0000000000..643a4750e1 Binary files /dev/null and b/submodules/WalletUI/Images.xcassets/Wallet/BalanceGem.imageset/gem.pdf differ diff --git a/submodules/WalletUI/Images.xcassets/Wallet/CameraFlashIcon.imageset/Contents.json b/submodules/WalletUI/Images.xcassets/Wallet/CameraFlashIcon.imageset/Contents.json new file mode 100644 index 0000000000..39f252e34f --- /dev/null +++ b/submodules/WalletUI/Images.xcassets/Wallet/CameraFlashIcon.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_flash.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/submodules/WalletUI/Images.xcassets/Wallet/CameraFlashIcon.imageset/ic_flash.pdf b/submodules/WalletUI/Images.xcassets/Wallet/CameraFlashIcon.imageset/ic_flash.pdf new file mode 100644 index 0000000000..68f60cae43 Binary files /dev/null and b/submodules/WalletUI/Images.xcassets/Wallet/CameraFlashIcon.imageset/ic_flash.pdf differ diff --git a/submodules/WalletUI/Images.xcassets/Wallet/CameraGalleryIcon.imageset/Contents.json b/submodules/WalletUI/Images.xcassets/Wallet/CameraGalleryIcon.imageset/Contents.json new file mode 100644 index 0000000000..393265de7f --- /dev/null +++ b/submodules/WalletUI/Images.xcassets/Wallet/CameraGalleryIcon.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_gallery (3).pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/submodules/WalletUI/Images.xcassets/Wallet/CameraGalleryIcon.imageset/ic_gallery (3).pdf b/submodules/WalletUI/Images.xcassets/Wallet/CameraGalleryIcon.imageset/ic_gallery (3).pdf new file mode 100644 index 0000000000..a5b36548a8 Binary files /dev/null and b/submodules/WalletUI/Images.xcassets/Wallet/CameraGalleryIcon.imageset/ic_gallery (3).pdf differ diff --git a/submodules/WalletUI/Images.xcassets/Wallet/Contents.json b/submodules/WalletUI/Images.xcassets/Wallet/Contents.json new file mode 100644 index 0000000000..38f0c81fc2 --- /dev/null +++ b/submodules/WalletUI/Images.xcassets/Wallet/Contents.json @@ -0,0 +1,9 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "provides-namespace" : true + } +} \ No newline at end of file diff --git a/submodules/WalletUI/Images.xcassets/Wallet/DuckIcon.imageset/Contents.json b/submodules/WalletUI/Images.xcassets/Wallet/DuckIcon.imageset/Contents.json new file mode 100644 index 0000000000..a596598c09 --- /dev/null +++ b/submodules/WalletUI/Images.xcassets/Wallet/DuckIcon.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "duck.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/submodules/WalletUI/Images.xcassets/Wallet/DuckIcon.imageset/duck.pdf b/submodules/WalletUI/Images.xcassets/Wallet/DuckIcon.imageset/duck.pdf new file mode 100644 index 0000000000..4ee986711e Binary files /dev/null and b/submodules/WalletUI/Images.xcassets/Wallet/DuckIcon.imageset/duck.pdf differ diff --git a/submodules/WalletUI/Images.xcassets/Wallet/NavigationSettingsIcon.imageset/Contents.json b/submodules/WalletUI/Images.xcassets/Wallet/NavigationSettingsIcon.imageset/Contents.json new file mode 100644 index 0000000000..31daa63843 --- /dev/null +++ b/submodules/WalletUI/Images.xcassets/Wallet/NavigationSettingsIcon.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "NavigationSettingsIcon.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/submodules/WalletUI/Images.xcassets/Wallet/NavigationSettingsIcon.imageset/NavigationSettingsIcon.pdf b/submodules/WalletUI/Images.xcassets/Wallet/NavigationSettingsIcon.imageset/NavigationSettingsIcon.pdf new file mode 100644 index 0000000000..4f188553a0 Binary files /dev/null and b/submodules/WalletUI/Images.xcassets/Wallet/NavigationSettingsIcon.imageset/NavigationSettingsIcon.pdf differ diff --git a/submodules/WalletUI/Images.xcassets/Wallet/QrGem.imageset/Contents.json b/submodules/WalletUI/Images.xcassets/Wallet/QrGem.imageset/Contents.json new file mode 100644 index 0000000000..ae81ff437c --- /dev/null +++ b/submodules/WalletUI/Images.xcassets/Wallet/QrGem.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "QrGem@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "QrGem@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/submodules/WalletUI/Images.xcassets/Wallet/QrGem.imageset/QrGem@2x.png b/submodules/WalletUI/Images.xcassets/Wallet/QrGem.imageset/QrGem@2x.png new file mode 100644 index 0000000000..35d164f8b3 Binary files /dev/null and b/submodules/WalletUI/Images.xcassets/Wallet/QrGem.imageset/QrGem@2x.png differ diff --git a/submodules/WalletUI/Images.xcassets/Wallet/QrGem.imageset/QrGem@3x.png b/submodules/WalletUI/Images.xcassets/Wallet/QrGem.imageset/QrGem@3x.png new file mode 100644 index 0000000000..6f2e9e6ed6 Binary files /dev/null and b/submodules/WalletUI/Images.xcassets/Wallet/QrGem.imageset/QrGem@3x.png differ diff --git a/submodules/WalletUI/Images.xcassets/Wallet/QrIcon.imageset/Contents.json b/submodules/WalletUI/Images.xcassets/Wallet/QrIcon.imageset/Contents.json new file mode 100644 index 0000000000..9bead22d1f --- /dev/null +++ b/submodules/WalletUI/Images.xcassets/Wallet/QrIcon.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_qrcode.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/submodules/WalletUI/Images.xcassets/Wallet/QrIcon.imageset/ic_qrcode.pdf b/submodules/WalletUI/Images.xcassets/Wallet/QrIcon.imageset/ic_qrcode.pdf new file mode 100644 index 0000000000..cf234b8223 Binary files /dev/null and b/submodules/WalletUI/Images.xcassets/Wallet/QrIcon.imageset/ic_qrcode.pdf differ diff --git a/submodules/WalletUI/Images.xcassets/Wallet/ReceiveButtonIcon.imageset/Contents.json b/submodules/WalletUI/Images.xcassets/Wallet/ReceiveButtonIcon.imageset/Contents.json new file mode 100644 index 0000000000..aca2fcc163 --- /dev/null +++ b/submodules/WalletUI/Images.xcassets/Wallet/ReceiveButtonIcon.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "Group2.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/submodules/WalletUI/Images.xcassets/Wallet/ReceiveButtonIcon.imageset/Group2.pdf b/submodules/WalletUI/Images.xcassets/Wallet/ReceiveButtonIcon.imageset/Group2.pdf new file mode 100644 index 0000000000..1f90a5f60c Binary files /dev/null and b/submodules/WalletUI/Images.xcassets/Wallet/ReceiveButtonIcon.imageset/Group2.pdf differ diff --git a/submodules/WalletUI/Images.xcassets/Wallet/RefreshIcon.imageset/Contents.json b/submodules/WalletUI/Images.xcassets/Wallet/RefreshIcon.imageset/Contents.json new file mode 100644 index 0000000000..0940c4ae6d --- /dev/null +++ b/submodules/WalletUI/Images.xcassets/Wallet/RefreshIcon.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_walletupdate.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/submodules/WalletUI/Images.xcassets/Wallet/RefreshIcon.imageset/ic_walletupdate.pdf b/submodules/WalletUI/Images.xcassets/Wallet/RefreshIcon.imageset/ic_walletupdate.pdf new file mode 100644 index 0000000000..62702732c7 Binary files /dev/null and b/submodules/WalletUI/Images.xcassets/Wallet/RefreshIcon.imageset/ic_walletupdate.pdf differ diff --git a/submodules/WalletUI/Images.xcassets/Wallet/SendButtonIcon.imageset/Contents.json b/submodules/WalletUI/Images.xcassets/Wallet/SendButtonIcon.imageset/Contents.json new file mode 100644 index 0000000000..10de2f9a6b --- /dev/null +++ b/submodules/WalletUI/Images.xcassets/Wallet/SendButtonIcon.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "Group.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/submodules/WalletUI/Images.xcassets/Wallet/SendButtonIcon.imageset/Group.pdf b/submodules/WalletUI/Images.xcassets/Wallet/SendButtonIcon.imageset/Group.pdf new file mode 100644 index 0000000000..7d26197899 Binary files /dev/null and b/submodules/WalletUI/Images.xcassets/Wallet/SendButtonIcon.imageset/Group.pdf differ diff --git a/submodules/WalletUI/Images.xcassets/Wallet/TransactionGem.imageset/Contents.json b/submodules/WalletUI/Images.xcassets/Wallet/TransactionGem.imageset/Contents.json new file mode 100644 index 0000000000..b5a8966f3f --- /dev/null +++ b/submodules/WalletUI/Images.xcassets/Wallet/TransactionGem.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "SmallGem.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/submodules/WalletUI/Images.xcassets/Wallet/TransactionGem.imageset/SmallGem.pdf b/submodules/WalletUI/Images.xcassets/Wallet/TransactionGem.imageset/SmallGem.pdf new file mode 100644 index 0000000000..cf1ac9ab53 Binary files /dev/null and b/submodules/WalletUI/Images.xcassets/Wallet/TransactionGem.imageset/SmallGem.pdf differ diff --git a/submodules/TelegramUI/TelegramUI/Resources/WalletAnimations/SendingGrams.tgs b/submodules/WalletUI/Resources/Animations/SendingGrams.tgs similarity index 100% rename from submodules/TelegramUI/TelegramUI/Resources/WalletAnimations/SendingGrams.tgs rename to submodules/WalletUI/Resources/Animations/SendingGrams.tgs diff --git a/submodules/WalletUI/Resources/Animations/WalletApologiesAccepted.tgs b/submodules/WalletUI/Resources/Animations/WalletApologiesAccepted.tgs new file mode 100755 index 0000000000..614c2fe0e7 Binary files /dev/null and b/submodules/WalletUI/Resources/Animations/WalletApologiesAccepted.tgs differ diff --git a/submodules/TelegramUI/TelegramUI/Resources/WalletAnimations/WalletCreated.tgs b/submodules/WalletUI/Resources/Animations/WalletCreated.tgs similarity index 100% rename from submodules/TelegramUI/TelegramUI/Resources/WalletAnimations/WalletCreated.tgs rename to submodules/WalletUI/Resources/Animations/WalletCreated.tgs diff --git a/submodules/TelegramUI/TelegramUI/Resources/WalletAnimations/WalletDone.tgs b/submodules/WalletUI/Resources/Animations/WalletDone.tgs similarity index 100% rename from submodules/TelegramUI/TelegramUI/Resources/WalletAnimations/WalletDone.tgs rename to submodules/WalletUI/Resources/Animations/WalletDone.tgs diff --git a/submodules/TelegramUI/TelegramUI/Resources/WalletAnimations/WalletEmpty.tgs b/submodules/WalletUI/Resources/Animations/WalletEmpty.tgs similarity index 100% rename from submodules/TelegramUI/TelegramUI/Resources/WalletAnimations/WalletEmpty.tgs rename to submodules/WalletUI/Resources/Animations/WalletEmpty.tgs diff --git a/submodules/TelegramUI/TelegramUI/Resources/WalletAnimations/WalletIntroLoading.tgs b/submodules/WalletUI/Resources/Animations/WalletIntroLoading.tgs similarity index 100% rename from submodules/TelegramUI/TelegramUI/Resources/WalletAnimations/WalletIntroLoading.tgs rename to submodules/WalletUI/Resources/Animations/WalletIntroLoading.tgs diff --git a/submodules/TelegramUI/TelegramUI/Resources/WalletAnimations/WalletIntroStatic.tgs b/submodules/WalletUI/Resources/Animations/WalletIntroStatic.tgs similarity index 100% rename from submodules/TelegramUI/TelegramUI/Resources/WalletAnimations/WalletIntroStatic.tgs rename to submodules/WalletUI/Resources/Animations/WalletIntroStatic.tgs diff --git a/submodules/TelegramUI/TelegramUI/Resources/WalletAnimations/WalletKeyLock.tgs b/submodules/WalletUI/Resources/Animations/WalletKeyLock.tgs similarity index 100% rename from submodules/TelegramUI/TelegramUI/Resources/WalletAnimations/WalletKeyLock.tgs rename to submodules/WalletUI/Resources/Animations/WalletKeyLock.tgs diff --git a/submodules/TelegramUI/TelegramUI/Resources/WalletAnimations/WalletNotAvailable.tgs b/submodules/WalletUI/Resources/Animations/WalletNotAvailable.tgs similarity index 100% rename from submodules/TelegramUI/TelegramUI/Resources/WalletAnimations/WalletNotAvailable.tgs rename to submodules/WalletUI/Resources/Animations/WalletNotAvailable.tgs diff --git a/submodules/TelegramUI/TelegramUI/Resources/WalletAnimations/WalletWordCheck.tgs b/submodules/WalletUI/Resources/Animations/WalletWordCheck.tgs similarity index 100% rename from submodules/TelegramUI/TelegramUI/Resources/WalletAnimations/WalletWordCheck.tgs rename to submodules/WalletUI/Resources/Animations/WalletWordCheck.tgs diff --git a/submodules/TelegramUI/TelegramUI/Resources/WalletAnimations/WalletWordList.tgs b/submodules/WalletUI/Resources/Animations/WalletWordList.tgs similarity index 100% rename from submodules/TelegramUI/TelegramUI/Resources/WalletAnimations/WalletWordList.tgs rename to submodules/WalletUI/Resources/Animations/WalletWordList.tgs diff --git a/submodules/WalletUI/Resources/WalletStrings.mapping b/submodules/WalletUI/Resources/WalletStrings.mapping index 1e50f90a2a..696b14c5b8 100644 Binary files a/submodules/WalletUI/Resources/WalletStrings.mapping and b/submodules/WalletUI/Resources/WalletStrings.mapping differ diff --git a/submodules/WalletUI/Sources/ItemList/ItemListControllerNode.swift b/submodules/WalletUI/Sources/ItemList/ItemListControllerNode.swift index 89661fdf44..72784e604b 100644 --- a/submodules/WalletUI/Sources/ItemList/ItemListControllerNode.swift +++ b/submodules/WalletUI/Sources/ItemList/ItemListControllerNode.swift @@ -3,7 +3,6 @@ import UIKit import AsyncDisplayKit import Display import SwiftSignalKit -import TelegramCore import MergeLists typealias ItemListSectionId = Int32 diff --git a/submodules/WalletUI/Sources/ItemList/Items/ItemListMultilineTextItem.swift b/submodules/WalletUI/Sources/ItemList/Items/ItemListMultilineTextItem.swift index cfa233a510..93e3f4c752 100644 --- a/submodules/WalletUI/Sources/ItemList/Items/ItemListMultilineTextItem.swift +++ b/submodules/WalletUI/Sources/ItemList/Items/ItemListMultilineTextItem.swift @@ -3,8 +3,6 @@ import UIKit import Display import AsyncDisplayKit import SwiftSignalKit -import TextFormat -import AccountContext enum ItemListMultilineTextBaseFont { case `default` @@ -14,22 +12,20 @@ enum ItemListMultilineTextBaseFont { class ItemListMultilineTextItem: ListViewItem, ItemListItem { let theme: WalletTheme let text: String - let enabledEntityTypes: EnabledEntityTypes let font: ItemListMultilineTextBaseFont let sectionId: ItemListSectionId let style: ItemListStyle let action: (() -> Void)? let longTapAction: (() -> Void)? - let linkItemAction: ((TextLinkItemActionType, TextLinkItem) -> Void)? + let linkItemAction: ((String) -> Void)? let tag: Any? let selectable: Bool - init(theme: WalletTheme, text: String, enabledEntityTypes: EnabledEntityTypes, font: ItemListMultilineTextBaseFont = .default, sectionId: ItemListSectionId, style: ItemListStyle, action: (() -> Void)? = nil, longTapAction: (() -> Void)? = nil, linkItemAction: ((TextLinkItemActionType, TextLinkItem) -> Void)? = nil, tag: Any? = nil) { + init(theme: WalletTheme, text: String, font: ItemListMultilineTextBaseFont = .default, sectionId: ItemListSectionId, style: ItemListStyle, action: (() -> Void)? = nil, longTapAction: (() -> Void)? = nil, linkItemAction: ((String) -> Void)? = nil, tag: Any? = nil) { self.theme = theme self.text = text - self.enabledEntityTypes = enabledEntityTypes self.font = font self.sectionId = sectionId self.style = style @@ -198,8 +194,7 @@ class ItemListMultilineTextItemNode: ListViewItemNode { boldItalicFont = Font.semiboldItalicMonospace(17.0) } - let entities = generateTextEntities(item.text, enabledTypes: item.enabledEntityTypes) - let string = stringWithAppliedEntities(item.text, entities: entities, baseColor: textColor, linkColor: item.theme.list.itemAccentColor, baseFont: baseFont, linkFont: linkFont, boldFont: boldFont, italicFont: italicFont, boldItalicFont: boldItalicFont, fixedFont: titleFixedFont, blockQuoteFont: titleFont) + let string = NSAttributedString(string: item.text, font: baseFont, textColor: textColor) let (titleLayout, titleApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: string, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset * 2.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) @@ -357,7 +352,7 @@ class ItemListMultilineTextItemNode: ListViewItemNode { switch gesture { case .tap, .longTap: if let item = self.item, let linkItem = self.linkItemAtPoint(location) { - item.linkItemAction?(gesture == .tap ? .tap : .longTap, linkItem) + item.linkItemAction?(linkItem) } default: break @@ -368,18 +363,10 @@ class ItemListMultilineTextItemNode: ListViewItemNode { } } - private func linkItemAtPoint(_ point: CGPoint) -> TextLinkItem? { + private func linkItemAtPoint(_ point: CGPoint) -> String? { let textNodeFrame = self.textNode.frame if let (_, attributes) = self.textNode.attributesAtPoint(CGPoint(x: point.x - textNodeFrame.minX, y: point.y - textNodeFrame.minY)) { - if let url = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String { - return .url(url) - } else if let peerName = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerTextMention)] as? String { - return .mention(peerName) - } else if let hashtag = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.Hashtag)] as? TelegramHashtag { - return .hashtag(hashtag.peerName, hashtag.hashtag) - } else { - return nil - } + return nil } return nil } @@ -395,11 +382,6 @@ class ItemListMultilineTextItemNode: ListViewItemNode { let textNodeFrame = self.textNode.frame if let (index, attributes) = self.textNode.attributesAtPoint(CGPoint(x: point.x - textNodeFrame.minX, y: point.y - textNodeFrame.minY)) { let possibleNames: [String] = [ - TelegramTextAttributes.URL, - TelegramTextAttributes.PeerMention, - TelegramTextAttributes.PeerTextMention, - TelegramTextAttributes.BotCommand, - TelegramTextAttributes.Hashtag ] for name in possibleNames { if let _ = attributes[NSAttributedString.Key(rawValue: name)] { diff --git a/submodules/WalletUI/Sources/ItemList/Items/ItemListTextItem.swift b/submodules/WalletUI/Sources/ItemList/Items/ItemListTextItem.swift index 058c960cf8..82cfe59bef 100644 --- a/submodules/WalletUI/Sources/ItemList/Items/ItemListTextItem.swift +++ b/submodules/WalletUI/Sources/ItemList/Items/ItemListTextItem.swift @@ -3,7 +3,7 @@ import UIKit import Display import AsyncDisplayKit import SwiftSignalKit -import TextFormat +import Markdown enum ItemListTextItemText { case plain(String) @@ -114,7 +114,7 @@ class ItemListTextItemNode: ListViewItemNode { attributedText = NSAttributedString(string: text, font: titleFont, textColor: item.theme.list.freeTextColor) case let .markdown(text): attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: titleFont, textColor: item.theme.list.freeTextColor), bold: MarkdownAttributeSet(font: titleBoldFont, textColor: item.theme.list.freeTextColor), link: MarkdownAttributeSet(font: titleFont, textColor: item.theme.list.itemAccentColor), linkAttribute: { contents in - return (TelegramTextAttributes.URL, contents) + return ("URL", contents) })) } let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: attributedText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - params.rightInset - leftInset * 2.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) @@ -160,7 +160,7 @@ class ItemListTextItemNode: ListViewItemNode { let titleFrame = self.titleNode.frame if let item = self.item, titleFrame.contains(location) { if let (_, attributes) = self.titleNode.attributesAtPoint(CGPoint(x: location.x - titleFrame.minX, y: location.y - titleFrame.minY)) { - if let url = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String { + if let url = attributes[NSAttributedString.Key(rawValue: "URL")] as? String { item.linkAction?(.tap(url)) } } diff --git a/submodules/WalletUI/Sources/ToastNode.swift b/submodules/WalletUI/Sources/ToastNode.swift index e69de29bb2..8ca7d38cfd 100644 --- a/submodules/WalletUI/Sources/ToastNode.swift +++ b/submodules/WalletUI/Sources/ToastNode.swift @@ -0,0 +1,78 @@ +import Foundation +import UIKit +import Display +import AsyncDisplayKit +import AnimatedStickerNode + +final class ToastNode: ASDisplayNode { + private let backgroundNode: ASDisplayNode + private let effectView: UIView + private let animationNode: AnimatedStickerNode + private let textNode: ImmediateTextNode + + init(theme: WalletTheme, animationPath: String, text: String) { + self.backgroundNode = ASDisplayNode() + self.backgroundNode.cornerRadius = 9.0 + self.backgroundNode.clipsToBounds = true + if case .dark = theme.keyboardAppearance { + self.backgroundNode.backgroundColor = theme.navigationBar.backgroundColor + } else { + self.backgroundNode.backgroundColor = .clear + } + + self.effectView = UIVisualEffectView(effect: UIBlurEffect(style: .dark)) + self.backgroundNode.view.addSubview(self.effectView) + + self.animationNode = AnimatedStickerNode() + self.animationNode.visibility = true + self.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(path: animationPath), width: 100, height: 100, playbackMode: .once, mode: .direct) + + self.textNode = ImmediateTextNode() + self.textNode.displaysAsynchronously = false + self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(14.0), textColor: .white) + self.textNode.maximumNumberOfLines = 2 + + super.init() + + self.addSubnode(self.backgroundNode) + self.addSubnode(self.animationNode) + self.addSubnode(self.textNode) + } + + func update(layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { + let contentSideInset: CGFloat = 10.0 + let contentVerticalInset: CGFloat = 8.0 + let iconSpacing: CGFloat = 4.0 + + let textSize = self.textNode.updateLayout(CGSize(width: layout.size.width - contentSideInset * 2.0, height: .greatestFiniteMagnitude)) + let iconSize = CGSize(width: 32.0, height: 32.0) + + let contentSize = CGSize(width: iconSize.width + iconSpacing + textSize.width, height: max(iconSize.height, textSize.height)) + + let insets = layout.insets(options: .input) + let contentOriginX = floor((layout.size.width - contentSize.width) / 2.0) + let contentOriginY = insets.top + floor((layout.size.height - insets.top - insets.bottom - contentSize.height) / 2.0) + + let iconFrame = CGRect(origin: CGPoint(x: contentOriginX, y: contentOriginY + floor((contentSize.height - iconSize.height) / 2.0)), size: iconSize) + transition.updateFrame(node: self.animationNode, frame: iconFrame) + self.animationNode.updateLayout(size: iconFrame.size) + + let textFrame = CGRect(origin: CGPoint(x: iconFrame.maxX + iconSpacing, y: contentOriginY + floor((contentSize.height - textSize.height) / 2.0)), size: textSize) + transition.updateFrame(node: self.textNode, frame: textFrame) + + let backgroundFrame = CGRect(origin: CGPoint(x: contentOriginX - contentSideInset, y: contentOriginY - contentVerticalInset), size: CGSize(width: contentSize.width + contentSideInset * 2.0, height: contentSize.height + contentVerticalInset * 2.0)) + transition.updateFrame(node: self.backgroundNode, frame: backgroundFrame) + transition.updateFrame(view: self.effectView, frame: CGRect(origin: CGPoint(), size: backgroundFrame.size)) + } + + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + return nil + } + + func show(removed: @escaping () -> Void) { + self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, delay: 3.0, removeOnCompletion: false, completion: { _ in + removed() + }) + } +} diff --git a/submodules/WalletUI/Sources/WalletConfiguration.swift b/submodules/WalletUI/Sources/WalletConfiguration.swift index 707a740cb5..0190db028d 100644 --- a/submodules/WalletUI/Sources/WalletConfiguration.swift +++ b/submodules/WalletUI/Sources/WalletConfiguration.swift @@ -1,8 +1,7 @@ import Foundation -import TelegramCore public struct WalletConfiguration { - static var defaultValue: WalletConfiguration { + public static var defaultValue: WalletConfiguration { return WalletConfiguration(config: nil, blockchainName: nil, disableProxy: false) } @@ -10,23 +9,9 @@ public struct WalletConfiguration { public let blockchainName: String? public let disableProxy: Bool - fileprivate init(config: String?, blockchainName: String?, disableProxy: Bool) { + public init(config: String?, blockchainName: String?, disableProxy: Bool) { self.config = config self.blockchainName = blockchainName self.disableProxy = disableProxy } - - public static func with(appConfiguration: AppConfiguration) -> WalletConfiguration { - if let data = appConfiguration.data, let config = data["wallet_config"] as? String, let blockchainName = data["wallet_blockchain_name"] as? String { - var disableProxy = false - if let value = data["wallet_disable_proxy"] as? String { - disableProxy = value != "0" - } else if let value = data["wallet_disable_proxy"] as? Int { - disableProxy = value != 0 - } - return WalletConfiguration(config: config, blockchainName: blockchainName, disableProxy: disableProxy) - } else { - return .defaultValue - } - } } diff --git a/submodules/WalletUI/Sources/WalletContext.swift b/submodules/WalletUI/Sources/WalletContext.swift index 5e280e040e..caed3f475c 100644 --- a/submodules/WalletUI/Sources/WalletContext.swift +++ b/submodules/WalletUI/Sources/WalletContext.swift @@ -1,18 +1,22 @@ import Foundation import UIKit -import Postbox -import TelegramCore import SwiftSignalKit +import WalletCore + +public enum WalletContextGetServerSaltError { + case generic +} public protocol WalletContext { - var postbox: Postbox { get } - var network: Network { get } + var storage: WalletStorageInterface { get } var tonInstance: TonInstance { get } var keychain: TonKeychain { get } var presentationData: WalletPresentationData { get } var inForeground: Signal { get } + func getServerSalt() -> Signal + func presentNativeController(_ controller: UIViewController) func idleTimerExtension() -> Disposable diff --git a/submodules/WalletUI/Sources/WalletCreateInvoiceScreen.swift b/submodules/WalletUI/Sources/WalletCreateInvoiceScreen.swift index 48fc296ef9..248e90b42a 100644 --- a/submodules/WalletUI/Sources/WalletCreateInvoiceScreen.swift +++ b/submodules/WalletUI/Sources/WalletCreateInvoiceScreen.swift @@ -3,8 +3,6 @@ import UIKit import AppBundle import AsyncDisplayKit import Display -import Postbox -import TelegramCore import SwiftSignalKit import OverlayStatusController @@ -237,7 +235,7 @@ private enum WalletCreateInvoiceScreenEntry: ItemListNodeEntry { case let .addressHeader(theme, text): return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section) case let .address(theme, text, monospace): - return ItemListMultilineTextItem(theme: theme, text: text, enabledEntityTypes: [], font: monospace ? .monospace : .default, sectionId: self.section, style: .blocks) + return ItemListMultilineTextItem(theme: theme, text: text, font: monospace ? .monospace : .default, sectionId: self.section, style: .blocks) case let .copyAddress(theme, text): return ItemListActionItem(theme: theme, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: { arguments.copyAddress() diff --git a/submodules/WalletUI/Sources/WalletInfoEmptyNode.swift b/submodules/WalletUI/Sources/WalletInfoEmptyNode.swift index 4636214a91..e53540b078 100644 --- a/submodules/WalletUI/Sources/WalletInfoEmptyNode.swift +++ b/submodules/WalletUI/Sources/WalletInfoEmptyNode.swift @@ -2,8 +2,7 @@ import Foundation import UIKit import Display import AsyncDisplayKit -import TelegramCore -import AnimationUI +import AnimatedStickerNode import SwiftSignalKit import AppBundle @@ -68,7 +67,7 @@ final class WalletInfoEmptyItemNode: ListViewItemNode { self.animationNode = AnimatedStickerNode() if let path = getAppBundle().path(forResource: "WalletEmpty", ofType: "tgs") { - self.animationNode.setup(resource: .localFile(path), width: 280, height: 280, playbackMode: .once, mode: .direct) + self.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(path: path), width: 280, height: 280, playbackMode: .once, mode: .direct) self.animationNode.visibility = true } diff --git a/submodules/WalletUI/Sources/WalletInfoScreen.swift b/submodules/WalletUI/Sources/WalletInfoScreen.swift index d2d74d4310..8426eeea1d 100644 --- a/submodules/WalletUI/Sources/WalletInfoScreen.swift +++ b/submodules/WalletUI/Sources/WalletInfoScreen.swift @@ -3,12 +3,11 @@ import UIKit import AppBundle import AsyncDisplayKit import Display -import Postbox -import TelegramCore import SolidRoundedButtonNode import SwiftSignalKit import MergeLists -import AnimationUI +import AnimatedStickerNode +import WalletCore public final class WalletInfoScreen: ViewController { private let context: WalletContext @@ -69,7 +68,9 @@ public final class WalletInfoScreen: ViewController { guard let strongSelf = self, let walletInfo = strongSelf.walletInfo else { return } - strongSelf.push(walletSendScreen(context: strongSelf.context, randomId: arc4random64(), walletInfo: walletInfo)) + var randomId: Int64 = 0 + arc4random_buf(&randomId, 8) + strongSelf.push(walletSendScreen(context: strongSelf.context, randomId: randomId, walletInfo: walletInfo)) }, receiveAction: { [weak self] in guard let strongSelf = self, let walletInfo = strongSelf.walletInfo else { return @@ -140,7 +141,7 @@ private final class WalletInfoBalanceNode: ASDisplayNode { self.balanceIconNode = AnimatedStickerNode() if let path = getAppBundle().path(forResource: "WalletIntroStatic", ofType: "tgs") { - self.balanceIconNode.setup(resource: .localFile(path), width: 120, height: 120, mode: .direct) + self.balanceIconNode.setup(source: AnimatedStickerNodeLocalFileSource(path: path), width: 120, height: 120, mode: .direct) self.balanceIconNode.visibility = true } @@ -606,12 +607,12 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode { if let walletInfo = walletInfo { subject = .wallet(walletInfo) - self.watchCombinedStateDisposable = (context.postbox.preferencesView(keys: [PreferencesKeys.walletCollection]) - |> deliverOnMainQueue).start(next: { [weak self] view in - guard let strongSelf = self, let wallets = view.values[PreferencesKeys.walletCollection] as? WalletCollection else { + self.watchCombinedStateDisposable = (context.storage.watchWalletRecords() + |> deliverOnMainQueue).start(next: { [weak self] records in + guard let strongSelf = self else { return } - for wallet in wallets.wallets { + for wallet in records { if wallet.info.publicKey == walletInfo.publicKey { if let state = wallet.state { if state.pendingTransactions != strongSelf.combinedState?.pendingTransactions || state.timestamp != strongSelf.combinedState?.timestamp { @@ -628,7 +629,7 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode { subject = .address(address) } let pollCombinedState: Signal = ( - getCombinedWalletState(postbox: context.postbox, subject: subject, tonInstance: context.tonInstance) + getCombinedWalletState(storage: context.storage, subject: subject, tonInstance: context.tonInstance) |> ignoreValues |> `catch` { _ -> Signal in return .complete() @@ -735,7 +736,7 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode { subject = .address(self.address) } - self.stateDisposable.set((getCombinedWalletState(postbox: self.context.postbox, subject: subject, tonInstance: self.context.tonInstance) + self.stateDisposable.set((getCombinedWalletState(storage: self.context.storage, subject: subject, tonInstance: self.context.tonInstance) |> deliverOnMainQueue).start(next: { [weak self] value in guard let strongSelf = self else { return diff --git a/submodules/WalletUI/Sources/WalletInfoTransactionItem.swift b/submodules/WalletUI/Sources/WalletInfoTransactionItem.swift index ec59ffaa74..2dcf846c7a 100644 --- a/submodules/WalletUI/Sources/WalletInfoTransactionItem.swift +++ b/submodules/WalletUI/Sources/WalletInfoTransactionItem.swift @@ -3,7 +3,6 @@ import UIKit import Display import AsyncDisplayKit import SwiftSignalKit -import TelegramCore private let transactionIcon = UIImage(bundleImageName: "Wallet/TransactionGem")?.precomposed() diff --git a/submodules/WalletUI/Sources/WalletPasscodeScreen.swift b/submodules/WalletUI/Sources/WalletPasscodeScreen.swift index a288cd0178..5be1703679 100644 --- a/submodules/WalletUI/Sources/WalletPasscodeScreen.swift +++ b/submodules/WalletUI/Sources/WalletPasscodeScreen.swift @@ -3,8 +3,6 @@ import UIKit import AppBundle import AsyncDisplayKit import Display -import Postbox -import TelegramCore import AnimationUI import SwiftSignalKit import OverlayStatusController diff --git a/submodules/WalletUI/Sources/WalletQrScanScreen.swift b/submodules/WalletUI/Sources/WalletQrScanScreen.swift index 30e2d8f88f..c2a8c72e2d 100644 --- a/submodules/WalletUI/Sources/WalletQrScanScreen.swift +++ b/submodules/WalletUI/Sources/WalletQrScanScreen.swift @@ -4,12 +4,11 @@ import AppBundle import AsyncDisplayKit import Display import SwiftSignalKit -import TelegramCore import Camera import GlassButtonNode -import UrlHandling import CoreImage import AlertUI +import WalletUrl private func generateFrameImage() -> UIImage? { return generateImage(CGSize(width: 64.0, height: 64.0), contextGenerator: { size, context in diff --git a/submodules/WalletUI/Sources/WalletQrViewScreen.swift b/submodules/WalletUI/Sources/WalletQrViewScreen.swift index 543cab2298..b083a8b4ea 100644 --- a/submodules/WalletUI/Sources/WalletQrViewScreen.swift +++ b/submodules/WalletUI/Sources/WalletQrViewScreen.swift @@ -4,10 +4,8 @@ import SwiftSignalKit import AppBundle import AsyncDisplayKit import Display -import Postbox import QrCode -import ShareController -import AnimationUI +import AnimatedStickerNode func shareInvoiceQrCode(context: WalletContext, invoice: String) { let _ = (qrCode(string: invoice, color: .black, backgroundColor: .white, icon: .custom(UIImage(bundleImageName: "Wallet/QrGem"))) @@ -127,7 +125,7 @@ private final class WalletQrViewScreenNode: ViewControllerTracingNode { self.iconNode = AnimatedStickerNode() if let path = getAppBundle().path(forResource: "WalletIntroStatic", ofType: "tgs") { - self.iconNode.setup(resource: .localFile(path), width: 240, height: 240, mode: .direct) + self.iconNode.setup(source: AnimatedStickerNodeLocalFileSource(path: path), width: 240, height: 240, mode: .direct) self.iconNode.visibility = true } diff --git a/submodules/WalletUI/Sources/WalletReceiveScreen.swift b/submodules/WalletUI/Sources/WalletReceiveScreen.swift index fbc47c55e9..cbf03e1f31 100644 --- a/submodules/WalletUI/Sources/WalletReceiveScreen.swift +++ b/submodules/WalletUI/Sources/WalletReceiveScreen.swift @@ -3,11 +3,8 @@ import UIKit import AppBundle import AsyncDisplayKit import Display -import Postbox -import TelegramCore import SwiftSignalKit import OverlayStatusController -import ShareController private final class WalletReceiveScreenArguments { let context: WalletContext @@ -141,7 +138,7 @@ private enum WalletReceiveScreenEntry: ItemListNodeEntry { case let .addressHeader(theme, text): return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section) case let .address(theme, text, monospace): - return ItemListMultilineTextItem(theme: theme, text: text, enabledEntityTypes: [], font: monospace ? .monospace : .default, sectionId: self.section, style: .blocks) + return ItemListMultilineTextItem(theme: theme, text: text, font: monospace ? .monospace : .default, sectionId: self.section, style: .blocks) case let .copyAddress(theme, text): return ItemListActionItem(theme: theme, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: { arguments.copyAddress() diff --git a/submodules/WalletUI/Sources/WalletSendScreen.swift b/submodules/WalletUI/Sources/WalletSendScreen.swift index 3883f7ac52..54f5510d2c 100644 --- a/submodules/WalletUI/Sources/WalletSendScreen.swift +++ b/submodules/WalletUI/Sources/WalletSendScreen.swift @@ -3,13 +3,12 @@ import UIKit import AppBundle import AsyncDisplayKit import Display -import Postbox -import TelegramCore import SwiftSignalKit import AlertUI -import TextFormat -import UrlHandling import OverlayStatusController +import WalletUrl +import WalletCore +import Markdown private let balanceIcon = UIImage(bundleImageName: "Wallet/TransactionGem")?.precomposed() @@ -281,7 +280,7 @@ public func walletSendScreen(context: WalletContext, randomId: Int64, walletInfo } let serverSaltValue = Promise() - serverSaltValue.set(getServerWalletSalt(network: context.network) + serverSaltValue.set(context.getServerSalt() |> map(Optional.init) |> `catch` { _ -> Signal in return .single(nil) @@ -419,8 +418,8 @@ public func walletSendScreen(context: WalletContext, randomId: Int64, walletInfo } }) - let walletState: Signal = getCombinedWalletState(postbox: context.postbox, subject: .wallet(walletInfo), tonInstance: context.tonInstance, onlyCached: true) - |> map { combinedState in + let walletState: Signal = getCombinedWalletState(storage: context.storage, subject: .wallet(walletInfo), tonInstance: context.tonInstance, onlyCached: true) + |> map { combinedState -> WalletState? in var state: WalletState? switch combinedState { case let .cached(combinedState): @@ -433,7 +432,7 @@ public func walletSendScreen(context: WalletContext, randomId: Int64, walletInfo |> `catch` { _ -> Signal in return .single(nil) |> then( - getCombinedWalletState(postbox: context.postbox, subject: .wallet(walletInfo), tonInstance: context.tonInstance, onlyCached: false) + getCombinedWalletState(storage: context.storage, subject: .wallet(walletInfo), tonInstance: context.tonInstance, onlyCached: false) |> map { combinedState -> WalletState? in var state: WalletState? switch combinedState { diff --git a/submodules/WalletUI/Sources/WalletSettingsScreen.swift b/submodules/WalletUI/Sources/WalletSettingsScreen.swift index 43ab4bd2f5..cc5edd862a 100644 --- a/submodules/WalletUI/Sources/WalletSettingsScreen.swift +++ b/submodules/WalletUI/Sources/WalletSettingsScreen.swift @@ -3,12 +3,10 @@ import UIKit import AppBundle import AsyncDisplayKit import Display -import Postbox -import TelegramCore import SolidRoundedButtonNode -import AnimationUI import SwiftSignalKit import OverlayStatusController +import WalletCore private final class WalletSettingsControllerArguments { let exportWallet: () -> Void @@ -104,7 +102,7 @@ public func walletSettingsController(context: WalletContext, walletInfo: WalletI presentControllerImpl?(controller, nil) let _ = (context.keychain.decrypt(walletInfo.encryptedSecret) |> deliverOnMainQueue).start(next: { [weak controller] decryptedSecret in - let _ = (getServerWalletSalt(network: context.network) + let _ = (context.getServerSalt() |> deliverOnMainQueue).start(next: { serverSalt in let _ = (walletRestoreWords(tonInstance: context.tonInstance, publicKey: walletInfo.publicKey, decryptedSecret: decryptedSecret, localPassword: serverSalt) |> deliverOnMainQueue).start(next: { [weak controller] wordList in @@ -128,7 +126,7 @@ public func walletSettingsController(context: WalletContext, walletInfo: WalletI actionSheet?.dismissAnimated() let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: nil)) presentControllerImpl?(controller, nil) - let _ = (deleteAllLocalWalletsData(postbox: context.postbox, network: context.network, tonInstance: context.tonInstance) + let _ = (deleteAllLocalWalletsData(storage: context.storage, tonInstance: context.tonInstance) |> deliverOnMainQueue).start(error: { [weak controller] _ in controller?.dismiss() }, completed: { [weak controller] in diff --git a/submodules/WalletUI/Sources/WalletSplashScreen.swift b/submodules/WalletUI/Sources/WalletSplashScreen.swift index e08a4e820f..7266ab037f 100644 --- a/submodules/WalletUI/Sources/WalletSplashScreen.swift +++ b/submodules/WalletUI/Sources/WalletSplashScreen.swift @@ -3,15 +3,14 @@ import UIKit import AppBundle import AsyncDisplayKit import Display -import Postbox -import TelegramCore import SolidRoundedButtonNode -import AnimationUI import SwiftSignalKit import OverlayStatusController import AlertUI -import TextFormat import LocalAuth +import AnimatedStickerNode +import WalletCore +import Markdown public enum WalletSecureStorageResetReason { case notAvailable @@ -51,7 +50,7 @@ public final class WalletSplashScreen: ViewController { self.walletCreatedPreloadState = walletCreatedPreloadState } else { self.walletCreatedPreloadState = Promise() - self.walletCreatedPreloadState?.set(getCombinedWalletState(postbox: context.postbox, subject: .wallet(walletInfo), tonInstance: context.tonInstance) + self.walletCreatedPreloadState?.set(getCombinedWalletState(storage: context.storage, subject: .wallet(walletInfo), tonInstance: context.tonInstance) |> map(Optional.init) |> `catch` { _ -> Signal in return .single(nil) @@ -62,7 +61,7 @@ public final class WalletSplashScreen: ViewController { self.walletCreatedPreloadState = walletCreatedPreloadState } else { self.walletCreatedPreloadState = Promise() - self.walletCreatedPreloadState?.set(getCombinedWalletState(postbox: context.postbox, subject: .wallet(walletInfo), tonInstance: context.tonInstance) + self.walletCreatedPreloadState?.set(getCombinedWalletState(storage: context.storage, subject: .wallet(walletInfo), tonInstance: context.tonInstance) |> map(Optional.init) |> `catch` { _ -> Signal in return .single(nil) @@ -131,7 +130,7 @@ public final class WalletSplashScreen: ViewController { } private func sendGrams(walletInfo: WalletInfo, decryptedSecret: Data, address: String, amount: Int64, textMessage: Data, forceIfDestinationNotInitialized: Bool, randomId: Int64, serverSalt: Data) { - let _ = (sendGramsFromWallet(postbox: self.context.postbox, network: self.context.network, tonInstance: self.context.tonInstance, walletInfo: walletInfo, decryptedSecret: decryptedSecret, localPassword: serverSalt, toAddress: address, amount: amount, textMessage: textMessage, forceIfDestinationNotInitialized: forceIfDestinationNotInitialized, timeout: 0, randomId: randomId) + let _ = (sendGramsFromWallet(storage: self.context.storage, tonInstance: self.context.tonInstance, walletInfo: walletInfo, decryptedSecret: decryptedSecret, localPassword: serverSalt, toAddress: address, amount: amount, textMessage: textMessage, forceIfDestinationNotInitialized: forceIfDestinationNotInitialized, timeout: 0, randomId: randomId) |> deliverOnMainQueue).start(error: { [weak self] error in guard let strongSelf = self else { return @@ -227,9 +226,9 @@ public final class WalletSplashScreen: ViewController { ], actionLayout: .vertical), in: .window(.root)) } strongSelf.present(controller, in: .window(.root)) - let _ = (getServerWalletSalt(network: strongSelf.context.network) + let _ = (strongSelf.context.getServerSalt() |> deliverOnMainQueue).start(next: { serverSalt in - let _ = (createWallet(postbox: strongSelf.context.postbox, tonInstance: strongSelf.context.tonInstance, keychain: strongSelf.context.keychain, localPassword: serverSalt) + let _ = (createWallet(storage: strongSelf.context.storage, tonInstance: strongSelf.context.tonInstance, keychain: strongSelf.context.keychain, localPassword: serverSalt) |> deliverOnMainQueue).start(next: { walletInfo, wordList in guard let strongSelf = self else { return @@ -252,7 +251,7 @@ public final class WalletSplashScreen: ViewController { let context = strongSelf.context let _ = (strongSelf.context.keychain.decrypt(walletInfo.encryptedSecret) |> deliverOnMainQueue).start(next: { [weak controller] decryptedSecret in - let _ = (getServerWalletSalt(network: context.network) + let _ = (context.getServerSalt() |> deliverOnMainQueue).start(next: { [weak controller] serverSalt in let _ = (walletRestoreWords(tonInstance: context.tonInstance, publicKey: walletInfo.publicKey, decryptedSecret: decryptedSecret, localPassword: serverSalt) |> deliverOnMainQueue).start(next: { wordList in @@ -539,7 +538,7 @@ private final class WalletSplashScreenNode: ViewControllerTracingNode { termsText = parseMarkdownIntoAttributedString(self.presentationData.strings.Wallet_Intro_Terms, attributes: MarkdownAttributes(body: body, bold: body, link: link, linkAttribute: { _ in nil }), textAlignment: .center) self.iconNode.image = nil if let path = getAppBundle().path(forResource: "WalletIntroLoading", ofType: "tgs") { - self.animationNode.setup(resource: .localFile(path), width: 248, height: 248, mode: .direct) + self.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(path: path), width: 248, height: 248, mode: .direct) self.animationSize = CGSize(width: 124.0, height: 124.0) self.animationNode.visibility = true } @@ -551,7 +550,7 @@ private final class WalletSplashScreenNode: ViewControllerTracingNode { termsText = NSAttributedString(string: "") self.iconNode.image = nil if let path = getAppBundle().path(forResource: "WalletCreated", ofType: "tgs") { - self.animationNode.setup(resource: .localFile(path), width: 250, height: 250, playbackMode: .once, mode: .direct) + self.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(path: path), width: 250, height: 250, playbackMode: .once, mode: .direct) self.animationSize = CGSize(width: 125.0, height: 125.0) self.animationNode.visibility = true } @@ -563,7 +562,7 @@ private final class WalletSplashScreenNode: ViewControllerTracingNode { termsText = NSAttributedString(string: "") self.iconNode.image = nil if let path = getAppBundle().path(forResource: "WalletDone", ofType: "tgs") { - self.animationNode.setup(resource: .localFile(path), width: 260, height: 260, playbackMode: .once, mode: .direct) + self.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(path: path), width: 260, height: 260, playbackMode: .once, mode: .direct) self.animationSize = CGSize(width: 130.0, height: 130.0) self.animationNode.visibility = true } @@ -575,7 +574,7 @@ private final class WalletSplashScreenNode: ViewControllerTracingNode { termsText = NSAttributedString(string: "") self.iconNode.image = nil if let path = getAppBundle().path(forResource: "WalletNotAvailable", ofType: "tgs") { - self.animationNode.setup(resource: .localFile(path), width: 260, height: 260, playbackMode: .once, mode: .direct) + self.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(path: path), width: 260, height: 260, playbackMode: .once, mode: .direct) self.animationSize = CGSize(width: 130.0, height: 130.0) self.animationNode.visibility = true } @@ -587,7 +586,7 @@ private final class WalletSplashScreenNode: ViewControllerTracingNode { termsText = NSAttributedString(string: "") self.iconNode.image = nil if let path = getAppBundle().path(forResource: "SendingGrams", ofType: "tgs") { - self.animationNode.setup(resource: .localFile(path), width: 260, height: 260, mode: .direct) + self.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(path: path), width: 260, height: 260, mode: .direct) self.animationSize = CGSize(width: 130.0, height: 130.0) self.animationNode.visibility = true } @@ -601,7 +600,7 @@ private final class WalletSplashScreenNode: ViewControllerTracingNode { termsText = NSAttributedString(string: "") self.iconNode.image = nil if let path = getAppBundle().path(forResource: "WalletDone", ofType: "tgs") { - self.animationNode.setup(resource: .localFile(path), width: 260, height: 260, playbackMode: .once, mode: .direct) + self.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(path: path), width: 260, height: 260, playbackMode: .once, mode: .direct) self.animationSize = CGSize(width: 130.0, height: 130.0) self.animationNode.visibility = true } @@ -613,7 +612,7 @@ private final class WalletSplashScreenNode: ViewControllerTracingNode { termsText = NSAttributedString(string: "") self.iconNode.image = nil if let path = getAppBundle().path(forResource: "WalletKeyLock", ofType: "tgs") { - self.animationNode.setup(resource: .localFile(path), width: 280, height: 280, playbackMode: .once, mode: .direct) + self.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(path: path), width: 280, height: 280, playbackMode: .once, mode: .direct) self.animationSize = CGSize(width: 140.0, height: 140.0) self.animationNode.visibility = true } @@ -658,7 +657,7 @@ private final class WalletSplashScreenNode: ViewControllerTracingNode { termsText = NSAttributedString(string: "") self.iconNode.image = nil if let path = getAppBundle().path(forResource: "WalletNotAvailable", ofType: "tgs") { - self.animationNode.setup(resource: .localFile(path), width: 260, height: 260, playbackMode: .once, mode: .direct) + self.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(path: path), width: 260, height: 260, playbackMode: .once, mode: .direct) self.animationSize = CGSize(width: 130.0, height: 130.0) self.animationNode.visibility = true } diff --git a/submodules/WalletUI/Sources/WalletStrings.swift b/submodules/WalletUI/Sources/WalletStrings.swift index 8e476626cd..c8ffa7e9bf 100644 --- a/submodules/WalletUI/Sources/WalletStrings.swift +++ b/submodules/WalletUI/Sources/WalletStrings.swift @@ -414,12 +414,12 @@ public final class WalletStrings: Equatable { public var Wallet_Info_RefreshErrorText: String { return self._s[188]! } public var Wallet_SecureStorageReset_Title: String { return self._s[189]! } public var Wallet_Receive_CommentHeader: String { return self._s[190]! } - public func Wallet_Updated_HoursAgo(_ value: Int32) -> String { + public func Wallet_Updated_MinutesAgo(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = walletStringsFormattedNumber(value, self.groupingSeparator) return String(format: self._ps[0 * 6 + Int(form.rawValue)]!, stringValue) } - public func Wallet_Updated_MinutesAgo(_ value: Int32) -> String { + public func Wallet_Updated_HoursAgo(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = walletStringsFormattedNumber(value, self.groupingSeparator) return String(format: self._ps[1 * 6 + Int(form.rawValue)]!, stringValue) diff --git a/submodules/WalletUI/Sources/WalletTransactionInfoScreen.swift b/submodules/WalletUI/Sources/WalletTransactionInfoScreen.swift index 47fac86e61..6a836f01e1 100644 --- a/submodules/WalletUI/Sources/WalletTransactionInfoScreen.swift +++ b/submodules/WalletUI/Sources/WalletTransactionInfoScreen.swift @@ -3,12 +3,10 @@ import UIKit import AppBundle import AsyncDisplayKit import Display -import Postbox -import TelegramCore import SolidRoundedButtonNode -import AnimationUI import SwiftSignalKit import OverlayStatusController +import WalletCore private func stringForFullDate(timestamp: Int32, strings: WalletStrings, dateTimeFormat: WalletPresentationDateTimeFormat) -> String { var t: time_t = Int(timestamp) @@ -153,7 +151,7 @@ private enum WalletTransactionInfoEntry: ItemListNodeEntry { case let .infoHeader(theme, text): return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section) case let .infoAddress(theme, text, address): - return ItemListMultilineTextItem(theme: theme, text: text, enabledEntityTypes: [], font: .monospace, sectionId: self.section, style: .blocks, longTapAction: address == nil ? nil : { + return ItemListMultilineTextItem(theme: theme, text: text, font: .monospace, sectionId: self.section, style: .blocks, longTapAction: address == nil ? nil : { if let address = address { arguments.displayContextMenu(WalletTransactionInfoEntryTag.address, address) } @@ -169,7 +167,7 @@ private enum WalletTransactionInfoEntry: ItemListNodeEntry { case let .storageFeeHeader(theme, text): return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section) case let .storageFee(theme, text): - return ItemListMultilineTextItem(theme: theme, text: text, enabledEntityTypes: [], sectionId: self.section, style: .blocks, longTapAction: nil, tag: nil) + return ItemListMultilineTextItem(theme: theme, text: text, sectionId: self.section, style: .blocks, longTapAction: nil, tag: nil) case let .storageFeeInfo(theme, text): return ItemListTextItem(theme: theme, text: .markdown(text), sectionId: self.section, linkAction: { action in switch action { @@ -180,7 +178,7 @@ private enum WalletTransactionInfoEntry: ItemListNodeEntry { case let .otherFeeHeader(theme, text): return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section) case let .otherFee(theme, text): - return ItemListMultilineTextItem(theme: theme, text: text, enabledEntityTypes: [], sectionId: self.section, style: .blocks, longTapAction: nil, tag: nil) + return ItemListMultilineTextItem(theme: theme, text: text, sectionId: self.section, style: .blocks, longTapAction: nil, tag: nil) case let .otherFeeInfo(theme, text): return ItemListTextItem(theme: theme, text: .markdown(text), sectionId: self.section, linkAction: { action in switch action { @@ -191,7 +189,7 @@ private enum WalletTransactionInfoEntry: ItemListNodeEntry { case let .commentHeader(theme, text): return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section) case let .comment(theme, text): - return ItemListMultilineTextItem(theme: theme, text: text, enabledEntityTypes: [], sectionId: self.section, style: .blocks, longTapAction: { + return ItemListMultilineTextItem(theme: theme, text: text, sectionId: self.section, style: .blocks, longTapAction: { arguments.displayContextMenu(WalletTransactionInfoEntryTag.comment, text) }, tag: WalletTransactionInfoEntryTag.comment) } @@ -347,7 +345,9 @@ func walletTransactionInfoController(context: WalletContext, walletInfo: WalletI let address = extractAddress(walletTransaction) if case let .list(addresses) = address, let address = addresses.first { dismissImpl?() - pushImpl?(walletSendScreen(context: context, randomId: arc4random64(), walletInfo: walletInfo, address: address)) + var randomId: Int64 = 0 + arc4random_buf(&randomId, 8) + pushImpl?(walletSendScreen(context: context, randomId: randomId, walletInfo: walletInfo, address: address)) } }, displayContextMenu: { tag, text in displayContextMenuImpl?(tag, text) diff --git a/submodules/WalletUI/Sources/WalletUtils.swift b/submodules/WalletUI/Sources/WalletUtils.swift index f9ceebd325..2be5d60849 100644 --- a/submodules/WalletUI/Sources/WalletUtils.swift +++ b/submodules/WalletUI/Sources/WalletUtils.swift @@ -2,7 +2,7 @@ import Foundation import UrlEscaping let walletAddressLength: Int = 48 -let walletTextLimit: Int = 1024 +let walletTextLimit: Int = 512 func formatAddress(_ address: String) -> String { var address = address diff --git a/submodules/WalletUI/Sources/WalletWordCheckScreen.swift b/submodules/WalletUI/Sources/WalletWordCheckScreen.swift index 4d28ad4794..282f0e2fcb 100644 --- a/submodules/WalletUI/Sources/WalletWordCheckScreen.swift +++ b/submodules/WalletUI/Sources/WalletWordCheckScreen.swift @@ -3,14 +3,12 @@ import UIKit import AppBundle import AsyncDisplayKit import Display -import Postbox -import TelegramCore import SolidRoundedButtonNode -import UndoUI import AlertUI import SwiftSignalKit -import TextFormat -import AnimationUI +import AnimatedStickerNode +import WalletCore +import Markdown private let possibleWordList: [String] = [ "abandon", @@ -2138,7 +2136,7 @@ public final class WalletWordCheckScreen: ViewController { } return true } - let _ = confirmWalletExported(postbox: strongSelf.context.postbox, walletInfo: walletInfo).start() + let _ = confirmWalletExported(storage: strongSelf.context.storage, publicKey: walletInfo.publicKey).start() controllers.append(WalletSplashScreen(context: strongSelf.context, mode: .success(walletInfo), walletCreatedPreloadState: strongSelf.walletCreatedPreloadState)) strongSelf.view.endEditing(true) navigationController.setViewControllers(controllers, animated: true) @@ -2186,9 +2184,9 @@ public final class WalletWordCheckScreen: ViewController { ], actionLayout: .vertical), in: .window(.root)) } - let _ = (getServerWalletSalt(network: strongSelf.context.network) + let _ = (strongSelf.context.getServerSalt() |> deliverOnMainQueue).start(next: { serverSalt in - let _ = (importWallet(postbox: strongSelf.context.postbox, tonInstance: strongSelf.context.tonInstance, keychain: strongSelf.context.keychain, wordList: enteredWords, localPassword: serverSalt) + let _ = (importWallet(storage: strongSelf.context.storage, tonInstance: strongSelf.context.tonInstance, keychain: strongSelf.context.keychain, wordList: enteredWords, localPassword: serverSalt) |> deliverOnMainQueue).start(next: { walletInfo in guard let strongSelf = self else { return @@ -2624,7 +2622,7 @@ private final class WalletWordCheckScreenNode: ViewControllerTracingNode, UIScro buttonText = self.presentationData.strings.Wallet_WordCheck_Continue secondaryActionText = "" if let path = getAppBundle().path(forResource: "WalletWordCheck", ofType: "tgs") { - self.animationNode.setup(resource: .localFile(path), width: 238, height: 238, playbackMode: .once, mode: .direct) + self.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(path: path), width: 238, height: 238, playbackMode: .once, mode: .direct) self.animationNode.visibility = true } case .import: diff --git a/submodules/WalletUI/Sources/WalletWordDisplayScreen.swift b/submodules/WalletUI/Sources/WalletWordDisplayScreen.swift index fdac4921bf..2c4d4b68be 100644 --- a/submodules/WalletUI/Sources/WalletWordDisplayScreen.swift +++ b/submodules/WalletUI/Sources/WalletWordDisplayScreen.swift @@ -4,12 +4,10 @@ import AppBundle import SwiftSignalKit import AsyncDisplayKit import Display -import Postbox -import TelegramCore import SolidRoundedButtonNode -import UndoUI import AlertUI -import AnimationUI +import AnimatedStickerNode +import WalletCore public enum WalletWordDisplayScreenMode { case check @@ -79,7 +77,7 @@ public final class WalletWordDisplayScreen: ViewController { let deltaTime = Date().timeIntervalSince1970 - strongSelf.startTime let minimalTimeout: Double #if DEBUG - minimalTimeout = 1.0 + minimalTimeout = 60.0 #else minimalTimeout = 60.0 #endif @@ -88,10 +86,7 @@ public final class WalletWordDisplayScreen: ViewController { guard let strongSelf = self else { return } - if let path = getAppBundle().path(forResource: "thumbsup", ofType: "tgs") { - let controller = UndoOverlayController(presentationData: strongSelf.presentationData, content: UndoOverlayContent.emoji(path: path, text: strongSelf.presentationData.strings.Wallet_Words_NotDoneResponse), elevatedLayout: false, animateInAsReplacement: false, action: { _ in }) - strongSelf.present(controller, in: .current) - } + (strongSelf.displayNode as! WalletWordDisplayScreenNode).displayToast() })]), in: .window(.root)) } else { var wordIndices: [Int] = [] @@ -131,6 +126,9 @@ private final class WalletWordDisplayScreenNode: ViewControllerTracingNode, UISc private let wordNodes: [(ImmediateTextNode, ImmediateTextNode, ImmediateTextNode)] private let buttonNode: SolidRoundedButtonNode + private var toastNode: ToastNode? + + private var validLayout: (ContainerViewLayout, CGFloat)? private var navigationHeight: CGFloat? init(presentationData: WalletPresentationData, wordList: [String], action: @escaping () -> Void) { @@ -148,7 +146,7 @@ private final class WalletWordDisplayScreenNode: ViewControllerTracingNode, UISc self.animationNode = AnimatedStickerNode() if let path = getAppBundle().path(forResource: "WalletWordList", ofType: "tgs") { - self.animationNode.setup(resource: .localFile(path), width: 264, height: 264, playbackMode: .once, mode: .direct) + self.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(path: path), width: 264, height: 264, playbackMode: .once, mode: .direct) self.animationNode.visibility = true } @@ -283,6 +281,7 @@ private final class WalletWordDisplayScreenNode: ViewControllerTracingNode, UISc } func containerLayoutUpdated(layout: ContainerViewLayout, navigationHeight: CGFloat, transition: ContainedViewLayoutTransition) { + self.validLayout = (layout, navigationHeight) self.navigationHeight = navigationHeight let sideInset: CGFloat = 32.0 @@ -385,4 +384,28 @@ private final class WalletWordDisplayScreenNode: ViewControllerTracingNode, UISc self.updateTitle() } + + func displayToast() { + if self.toastNode != nil { + return + } + + if let path = getAppBundle().path(forResource: "WalletApologiesAccepted", ofType: "tgs") { + let toastNode = ToastNode(theme: self.presentationData.theme, animationPath: path, text: self.presentationData.strings.Wallet_Words_NotDoneResponse) + self.toastNode = toastNode + if let (layout, navigationHeight) = self.validLayout { + toastNode.update(layout: layout, transition: .immediate) + } + self.addSubnode(toastNode) + toastNode.show(removed: { [weak self, weak toastNode] in + guard let strongSelf = self, let toastNode = toastNode else { + return + } + toastNode.removeFromSupernode() + if toastNode === strongSelf.toastNode { + strongSelf.toastNode = nil + } + }) + } + } } diff --git a/submodules/WalletUrl/BUCK b/submodules/WalletUrl/BUCK new file mode 100644 index 0000000000..f7042f93d4 --- /dev/null +++ b/submodules/WalletUrl/BUCK @@ -0,0 +1,14 @@ +load("//Config:buck_rule_macros.bzl", "static_library") + +static_library( + name = "WalletUrl", + srcs = glob([ + "Sources/**/*.swift", + ]), + deps = [ + + ], + frameworks = [ + "$SDKROOT/System/Library/Frameworks/Foundation.framework", + ], +) diff --git a/submodules/WalletUrl/Sources/WalletUrl.swift b/submodules/WalletUrl/Sources/WalletUrl.swift new file mode 100644 index 0000000000..e55cdd0d18 --- /dev/null +++ b/submodules/WalletUrl/Sources/WalletUrl.swift @@ -0,0 +1,40 @@ +import Foundation + +public struct ParsedWalletUrl { + public let address: String + public let amount: Int64? + public let comment: String? +} + +private let invalidWalletAddressCharacters = CharacterSet(charactersIn: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_=").inverted +private func isValidWalletAddress(_ address: String) -> Bool { + if address.count != 48 || address.rangeOfCharacter(from: invalidWalletAddressCharacters) != nil { + return false + } + return true +} + +public func parseWalletUrl(_ url: URL) -> ParsedWalletUrl? { + guard url.scheme == "ton" && url.host == "transfer" else { + return nil + } + var address: String? + let path = url.path.trimmingCharacters(in: CharacterSet(charactersIn: "/")) + if isValidWalletAddress(path) { + address = path + } + var amount: Int64? + var comment: String? + if let query = url.query, let components = URLComponents(string: "/?" + query), let queryItems = components.queryItems { + for queryItem in queryItems { + if let value = queryItem.value { + if queryItem.name == "amount", !value.isEmpty, let amountValue = Int64(value) { + amount = amountValue + } else if queryItem.name == "text", !value.isEmpty { + comment = value + } + } + } + } + return address.flatMap { ParsedWalletUrl(address: $0, amount: amount, comment: comment) } +} diff --git a/submodules/WebSearchUI/BUCK b/submodules/WebSearchUI/BUCK index f9bd159378..3ff9abbe7e 100644 --- a/submodules/WebSearchUI/BUCK +++ b/submodules/WebSearchUI/BUCK @@ -26,6 +26,7 @@ static_library( "//submodules/LegacyMediaPickerUI:LegacyMediaPickerUI", "//submodules/SegmentedControlNode:SegmentedControlNode", "//submodules/AppBundle:AppBundle", + "//submodules/PresentationDataUtils:PresentationDataUtils", ], frameworks = [ "$SDKROOT/System/Library/Frameworks/Foundation.framework", diff --git a/tools/GenerateLocalization.sh b/tools/GenerateLocalization.sh index 326e5ce5b8..203a1ed412 100644 --- a/tools/GenerateLocalization.sh +++ b/tools/GenerateLocalization.sh @@ -5,3 +5,13 @@ swift -swift-version 4 tools/GenerateLocalization.swift Telegram-iOS/en.lproj/Lo mkdir -p submodules/WalletUI/Resources swift -swift-version 4 tools/GenerateLocalization.swift Telegram-iOS/en.lproj/Localizable.strings submodules/WalletUI/Sources/WalletStrings.swift submodules/WalletUI/Resources/WalletStrings.mapping "Wallet." +wallet_strings_path="Wallet/Strings" +strings_name="Localizable.strings" +rm -rf "$wallet_strings_path" + +for f in $(basename $(find "Telegram-iOS" -name "*.lproj")); do + mkdir -p "$wallet_strings_path/$f" + if [ -f "Telegram-iOS/$f/$strings_name" ]; then + cat "Telegram-iOS/$f/$strings_name" | grep -E '^"Wallet\..*?$' > "$wallet_strings_path/$f/$strings_name" + fi +done